diff options
| -rw-r--r-- | Documentation/devicetree/bindings/power/supply/qcom/qpnp-fg.txt | 103 | ||||
| -rw-r--r-- | drivers/power/supply/qcom/qpnp-fg.c | 2981 |
2 files changed, 2670 insertions, 414 deletions
diff --git a/Documentation/devicetree/bindings/power/supply/qcom/qpnp-fg.txt b/Documentation/devicetree/bindings/power/supply/qcom/qpnp-fg.txt index f6a7a1ba3005..1e44686b6943 100644 --- a/Documentation/devicetree/bindings/power/supply/qcom/qpnp-fg.txt +++ b/Documentation/devicetree/bindings/power/supply/qcom/qpnp-fg.txt @@ -109,6 +109,10 @@ Parent node optional properties: this. If this property is not specified, low battery voltage threshold will be configured to 4200 mV. +- qcom,fg-rconn-mohm: Battery connector resistance (Rconn) in + milliohms. If Rconn is specified, then + Rslow values will be updated to account + it for an accurate ESR. - qcom,cycle-counter-en: Boolean property which enables the cycle counter feature. If this property is present, then the following properties @@ -143,6 +147,14 @@ Parent node optional properties: battery voltage shadow and the current predicted voltage in uV to initiate capacity learning. +- qcom,cl-max-limit-deciperc: The maximum percent that the capacity + cannot go above during any capacity + learning cycle. This property is in the + unit of .1% increments. +- qcom,cl-min-limit-deciperc: The minimum percent that the capacity + cannot go below during any capacity + learning cycle. This property is in the + unit of .1% increments. - qcom,capacity-estimation-on: A boolean property to have the fuel gauge driver attempt to estimate the battery capacity using battery @@ -178,6 +190,97 @@ Parent node optional properties: settings will be different from default. Once SOC crosses 5%, ESR pulse timings will be restored back to default. +- qcom,fg-control-slope-limiter: A boolean property to specify if SOC + slope limiter coefficients needs to + be modified based on charging status + and battery temperature threshold. +- qcom,fg-slope-limit-temp-threshold: Temperature threshold in decidegC used + for applying the slope coefficient based + on charging status and battery + temperature. If this property is not + specified, a default value of 100 (10C) + will be applied by default. +- qcom,fg-slope-limit-low-temp-chg: When the temperature goes below the + specified temperature threshold and + battery is charging, slope coefficient + specified with this property will be + applied. If this property is not + specified, a default value of 45 will be + applied. +- qcom,fg-slope-limit-low-temp-dischg: Same as "qcom,fg-slope-limit-low-temp-chg" + except this is when the battery is + discharging. +- qcom,fg-slope-limit-high-temp-chg: When the temperature goes above the + specified temperature threshold and + battery is charging, slope coefficient + specified with this property will be + applied. If this property is not + specified, a default value of 2 will be + applied. +- qcom,fg-slope-limit-high-temp-dischg: Same as "qcom,fg-slope-limit-high-temp-chg" + except this is when the battery is + discharging. +- qcom,fg-dischg-voltage-gain-ctrl: A boolean property to specify if the + voltage gain needs to be modified + during discharging based on monotonic + soc. +- qcom,fg-dischg-voltage-gain-soc: Array of monotonic SOC threshold values + to change the voltage gain settings + during discharge. This should be defined + in the ascending order and in the range + of 0-100. Array limit is set to 3. + If qcom,fg-dischg-voltage-gain-ctrl is + set, then this property should be + specified to apply the gain settings. +- qcom,fg-dischg-med-voltage-gain: Array of voltage gain values that needs + to be applied to medC voltage gain when + the monotonic SOC goes below the SOC + threshold specified under + qcom,fg-dischg-voltage-gain-soc. Array + limit is set to 3. + If qcom,fg-dischg-voltage-gain-ctrl is + set, then this property should be + specified to apply the gain setting. +- qcom,fg-dischg-high-voltage-gain: Array of voltage gain values that needs + to be applied to highC voltage gain when + the monotonic SOC goes below the SOC + threshold specified under + qcom,fg-dischg-voltage-gain-soc. Array + limit is set to 3. + If qcom,fg-dischg-voltage-gain-ctrl is + set, then this property should be + specified to apply the gain setting. +- qcom,fg-use-vbat-low-empty-soc: A boolean property to specify whether + vbatt-low interrupt is used to handle + empty battery condition. If this is + not specified, empty battery condition + is detected by empty-soc interrupt. +- qcom,fg-batt-temp-low-limit: Battery temperature (in decidegC) low + limit which will be used to validate + the battery temperature reading from FG. + If the battery temperature goes below + this limit, last read good temperature + will be notified to userspace. If this + limit is not specified, then the + default limit would be -60C. +- qcom,fg-batt-temp-high-limit: Battery temperature (in decidegC) high + limit which will be used to validate + the battery temperature reading from FG. + If the battery temperature goes above + this limit, last read good temperature + will be notified to userspace. If this + limit is not specified, then the + default limit would be 150C. +- qcom,fg-cc-soc-limit-pct: Percentage of CC_SOC before resetting + FG and restore the full CC_SOC value. +- qcom,fg-restore-batt-info: A boolean property to specify whether + battery parameters needs to be + restored. If this feature is enabled, + then validating the battery parameters + by OCV/battery SOC, validation range + in percentage should be specified via + appropriate module parameters to make + it work properly. qcom,fg-soc node required properties: - reg : offset and length of the PMIC peripheral register map. diff --git a/drivers/power/supply/qcom/qpnp-fg.c b/drivers/power/supply/qcom/qpnp-fg.c index a12b0adfa32d..3ddc10689707 100644 --- a/drivers/power/supply/qcom/qpnp-fg.c +++ b/drivers/power/supply/qcom/qpnp-fg.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2014-2017, The Linux Foundation. All rights reserved. +/* Copyright (c) 2014-2017, 2019, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -33,6 +33,7 @@ #include <linux/ktime.h> #include <linux/power_supply.h> #include <linux/of_batterydata.h> +#include <linux/spinlock.h> #include <linux/string_helpers.h> #include <linux/alarmtimer.h> #include <linux/qpnp/qpnp-revid.h> @@ -72,6 +73,7 @@ #define QPNP_FG_DEV_NAME "qcom,qpnp-fg" #define MEM_IF_TIMEOUT_MS 5000 +#define FG_CYCLE_MS 1500 #define BUCKET_COUNT 8 #define BUCKET_SOC_PCT (256 / BUCKET_COUNT) @@ -108,6 +110,7 @@ enum pmic_subtype { PMI8950 = 17, PMI8996 = 19, PMI8937 = 55, + PMI8940 = 64, }; enum wa_flags { @@ -150,6 +153,8 @@ struct fg_learning_data { int min_temp; int max_temp; int vbat_est_thr_uv; + int max_cap_limit; + int min_cap_limit; }; struct fg_rslow_data { @@ -275,11 +280,45 @@ static struct fg_mem_data fg_data[FG_DATA_MAX] = { DATA(BATT_ID_INFO, 0x594, 3, 1, -EINVAL), }; +enum fg_mem_backup_index { + FG_BACKUP_SOC = 0, + FG_BACKUP_CYCLE_COUNT, + FG_BACKUP_CC_SOC_COEFF, + FG_BACKUP_IGAIN, + FG_BACKUP_VCOR, + FG_BACKUP_TEMP_COUNTER, + FG_BACKUP_AGING_STORAGE, + FG_BACKUP_MAH_TO_SOC, + FG_BACKUP_MAX, +}; + +#define BACKUP(_idx, _address, _offset, _length, _value) \ + [FG_BACKUP_##_idx] = { \ + .address = _address, \ + .offset = _offset, \ + .len = _length, \ + .value = _value, \ + } \ + +static struct fg_mem_data fg_backup_regs[FG_BACKUP_MAX] = { + /* ID Address, Offset, Length, Value*/ + BACKUP(SOC, 0x564, 0, 24, -EINVAL), + BACKUP(CYCLE_COUNT, 0x5E8, 0, 16, -EINVAL), + BACKUP(CC_SOC_COEFF, 0x5BC, 0, 8, -EINVAL), + BACKUP(IGAIN, 0x424, 0, 4, -EINVAL), + BACKUP(VCOR, 0x484, 0, 4, -EINVAL), + BACKUP(TEMP_COUNTER, 0x580, 0, 4, -EINVAL), + BACKUP(AGING_STORAGE, 0x5E4, 0, 4, -EINVAL), + BACKUP(MAH_TO_SOC, 0x4A0, 0, 4, -EINVAL), +}; + static int fg_debug_mask; module_param_named( debug_mask, fg_debug_mask, int, 00600 ); +static int fg_reset_on_lockup; + static int fg_sense_type = -EINVAL; static int fg_restart; @@ -298,9 +337,18 @@ module_param_named( sram_update_period_ms, fg_sram_update_period_ms, int, 00600 ); +static bool fg_batt_valid_ocv; +module_param_named(batt_valid_ocv, fg_batt_valid_ocv, bool, 0600 +); + +static int fg_batt_range_pct; +module_param_named(batt_range_pct, fg_batt_range_pct, int, 0600 +); + struct fg_irq { int irq; - unsigned long disabled; + bool disabled; + bool wakeup; }; enum fg_soc_irq { @@ -348,6 +396,16 @@ enum register_type { MAX_ADDRESS, }; +enum batt_info_params { + BATT_INFO_NOTIFY = 0, + BATT_INFO_SOC, + BATT_INFO_RES_ID, + BATT_INFO_VOLTAGE, + BATT_INFO_TEMP, + BATT_INFO_FCC, + BATT_INFO_MAX, +}; + struct register_offset { u16 address[MAX_ADDRESS]; }; @@ -395,6 +453,22 @@ static void fg_relax(struct fg_wakeup_source *source) } } +enum slope_limit_status { + LOW_TEMP_CHARGE, + HIGH_TEMP_CHARGE, + LOW_TEMP_DISCHARGE, + HIGH_TEMP_DISCHARGE, + SLOPE_LIMIT_MAX, +}; + +#define VOLT_GAIN_MAX 3 +struct dischg_gain_soc { + bool enable; + u32 soc[VOLT_GAIN_MAX]; + u32 medc_gain[VOLT_GAIN_MAX]; + u32 highc_gain[VOLT_GAIN_MAX]; +}; + #define THERMAL_COEFF_N_BYTES 6 struct fg_chip { struct device *dev; @@ -420,6 +494,7 @@ struct fg_chip { struct completion first_soc_done; struct power_supply *bms_psy; struct power_supply_desc bms_psy_d; + spinlock_t sec_access_lock; struct mutex rw_lock; struct mutex sysfs_restart_lock; struct delayed_work batt_profile_init; @@ -449,6 +524,7 @@ struct fg_chip { struct fg_wakeup_source update_sram_wakeup_source; bool fg_restarting; bool profile_loaded; + bool soc_reporting_ready; bool use_otp_profile; bool battery_missing; bool power_supply_registered; @@ -459,6 +535,7 @@ struct fg_chip { bool charge_done; bool resume_soc_lowered; bool vbat_low_irq_enabled; + bool full_soc_irq_enabled; bool charge_full; bool hold_soc_while_full; bool input_present; @@ -467,6 +544,10 @@ struct fg_chip { bool bad_batt_detection_en; bool bcl_lpm_disabled; bool charging_disabled; + bool use_vbat_low_empty_soc; + bool fg_shutdown; + bool use_soft_jeita_irq; + bool allow_false_negative_isense; struct delayed_work update_jeita_setting; struct delayed_work update_sram_data; struct delayed_work update_temp_work; @@ -491,6 +572,7 @@ struct fg_chip { int prev_status; int health; enum fg_batt_aging_mode batt_aging_mode; + struct alarm hard_jeita_alarm; /* capacity learning */ struct fg_learning_data learning_data; struct alarm fg_cap_learning_alarm; @@ -498,6 +580,7 @@ struct fg_chip { struct fg_cc_soc_data sw_cc_soc_data; /* rslow compensation */ struct fg_rslow_data rslow_comp; + int rconn_mohm; /* cycle counter */ struct fg_cyc_ctr_data cyc_ctr; /* iadc compensation */ @@ -510,6 +593,8 @@ struct fg_chip { bool jeita_hysteresis_support; bool batt_hot; bool batt_cold; + bool batt_warm; + bool batt_cool; int cold_hysteresis; int hot_hysteresis; /* ESR pulse tuning */ @@ -518,6 +603,47 @@ struct fg_chip { bool esr_extract_disabled; bool imptr_pulse_slow_en; bool esr_pulse_tune_en; + /* Slope limiter */ + struct work_struct slope_limiter_work; + struct fg_wakeup_source slope_limit_wakeup_source; + bool soc_slope_limiter_en; + enum slope_limit_status slope_limit_sts; + u32 slope_limit_temp; + u32 slope_limit_coeffs[SLOPE_LIMIT_MAX]; + /* Discharge soc gain */ + struct work_struct dischg_gain_work; + struct fg_wakeup_source dischg_gain_wakeup_source; + struct dischg_gain_soc dischg_gain; + /* IMA error recovery */ + struct completion fg_reset_done; + struct work_struct ima_error_recovery_work; + struct fg_wakeup_source fg_reset_wakeup_source; + struct mutex ima_recovery_lock; + bool ima_error_handling; + bool block_sram_access; + bool irqs_enabled; + bool use_last_soc; + int last_soc; + /* Validating temperature */ + int last_good_temp; + int batt_temp_low_limit; + int batt_temp_high_limit; + /* Validating CC_SOC */ + struct work_struct cc_soc_store_work; + struct fg_wakeup_source cc_soc_wakeup_source; + int cc_soc_limit_pct; + bool use_last_cc_soc; + int64_t last_cc_soc; + /* Sanity check */ + struct delayed_work check_sanity_work; + struct fg_wakeup_source sanity_wakeup_source; + u8 last_beat_count; + /* Batt_info restore */ + int batt_info[BATT_INFO_MAX]; + int batt_info_id; + bool batt_info_restore; + bool *batt_range_ocv; + int *batt_range_pct; }; /* FG_MEMIF DEBUGFS structures */ @@ -661,17 +787,56 @@ static int fg_read(struct fg_chip *chip, u8 *val, u16 addr, int len) return rc; } -static int fg_masked_write(struct fg_chip *chip, u16 addr, +static int fg_masked_write_raw(struct fg_chip *chip, u16 addr, u8 mask, u8 val, int len) { int rc; rc = regmap_update_bits(chip->regmap, addr, mask, val); - if (rc) { + if (rc) pr_err("spmi write failed: addr=%03X, rc=%d\n", addr, rc); - return rc; + + return rc; +} + +static int fg_masked_write(struct fg_chip *chip, u16 addr, + u8 mask, u8 val, int len) +{ + int rc; + unsigned long flags; + + spin_lock_irqsave(&chip->sec_access_lock, flags); + rc = fg_masked_write_raw(chip, addr, mask, val, len); + spin_unlock_irqrestore(&chip->sec_access_lock, flags); + + return rc; +} + +#define SEC_ACCESS_OFFSET 0xD0 +#define SEC_ACCESS_VALUE 0xA5 +#define PERIPHERAL_MASK 0xFF +static int fg_sec_masked_write(struct fg_chip *chip, u16 addr, u8 mask, u8 val, + int len) +{ + int rc; + unsigned long flags; + u8 temp; + u16 base = addr & (~PERIPHERAL_MASK); + + spin_lock_irqsave(&chip->sec_access_lock, flags); + temp = SEC_ACCESS_VALUE; + rc = fg_write(chip, &temp, base + SEC_ACCESS_OFFSET, 1); + if (rc) { + pr_err("Unable to unlock sec_access: %d\n", rc); + goto out; } + rc = fg_masked_write_raw(chip, addr, mask, val, len); + if (rc) + pr_err("Unable to write securely to address 0x%x: %d", addr, + rc); +out: + spin_unlock_irqrestore(&chip->sec_access_lock, flags); return rc; } @@ -952,6 +1117,7 @@ static int fg_conventional_mem_write(struct fg_chip *chip, u8 *val, u16 address, int rc = 0, user_cnt = 0, sublen; bool access_configured = false; u8 *wr_data = val, word[4]; + u16 orig_address = address; char str[DEBUG_PRINT_BUFFER_SIZE]; if (address < RAM_OFFSET) @@ -960,8 +1126,8 @@ static int fg_conventional_mem_write(struct fg_chip *chip, u8 *val, u16 address, if (offset > 3) return -EINVAL; - address = ((address + offset) / 4) * 4; - offset = (address + offset) % 4; + address = ((orig_address + offset) / 4) * 4; + offset = (orig_address + offset) % 4; user_cnt = atomic_add_return(1, &chip->memif_user_cnt); if (fg_debug_mask & FG_MEM_DEBUG_WRITES) @@ -1061,50 +1227,253 @@ out: #define MEM_INTF_IMA_EXP_STS 0x55 #define MEM_INTF_IMA_HW_STS 0x56 #define MEM_INTF_IMA_BYTE_EN 0x60 -#define IMA_ADDR_STBL_ERR BIT(7) -#define IMA_WR_ACS_ERR BIT(6) -#define IMA_RD_ACS_ERR BIT(5) #define IMA_IACS_CLR BIT(2) #define IMA_IACS_RDY BIT(1) -static int fg_check_ima_exception(struct fg_chip *chip) +static int fg_run_iacs_clear_sequence(struct fg_chip *chip) +{ + int rc = 0; + u8 temp; + + if (fg_debug_mask & FG_STATUS) + pr_info("Running IACS clear sequence\n"); + + /* clear the error */ + rc = fg_masked_write(chip, chip->mem_base + MEM_INTF_IMA_CFG, + IMA_IACS_CLR, IMA_IACS_CLR, 1); + if (rc) { + pr_err("Error writing to IMA_CFG, rc=%d\n", rc); + return rc; + } + + temp = 0x4; + rc = fg_write(chip, &temp, MEM_INTF_ADDR_LSB(chip) + 1, 1); + if (rc) { + pr_err("Error writing to MEM_INTF_ADDR_MSB, rc=%d\n", rc); + return rc; + } + + temp = 0x0; + rc = fg_write(chip, &temp, MEM_INTF_WR_DATA0(chip) + 3, 1); + if (rc) { + pr_err("Error writing to WR_DATA3, rc=%d\n", rc); + return rc; + } + + rc = fg_read(chip, &temp, MEM_INTF_RD_DATA0(chip) + 3, 1); + if (rc) { + pr_err("Error writing to RD_DATA3, rc=%d\n", rc); + return rc; + } + + rc = fg_masked_write(chip, chip->mem_base + MEM_INTF_IMA_CFG, + IMA_IACS_CLR, 0, 1); + if (rc) { + pr_err("Error writing to IMA_CFG, rc=%d\n", rc); + return rc; + } + + if (fg_debug_mask & FG_STATUS) + pr_info("IACS clear sequence complete!\n"); + return rc; +} + +#define IACS_ERR_BIT BIT(0) +#define XCT_ERR_BIT BIT(1) +#define DATA_RD_ERR_BIT BIT(3) +#define DATA_WR_ERR_BIT BIT(4) +#define ADDR_BURST_WRAP_BIT BIT(5) +#define ADDR_RNG_ERR_BIT BIT(6) +#define ADDR_SRC_ERR_BIT BIT(7) +static int fg_check_ima_exception(struct fg_chip *chip, bool check_hw_sts) { int rc = 0, ret = 0; - u8 err_sts, exp_sts = 0, hw_sts = 0; + u8 err_sts = 0, exp_sts = 0, hw_sts = 0; + bool run_err_clr_seq = false; rc = fg_read(chip, &err_sts, chip->mem_base + MEM_INTF_IMA_ERR_STS, 1); if (rc) { - pr_err("failed to read beat count rc=%d\n", rc); + pr_err("failed to read IMA_ERR_STS, rc=%d\n", rc); return rc; } - if (err_sts & (IMA_ADDR_STBL_ERR | IMA_WR_ACS_ERR | IMA_RD_ACS_ERR)) { - u8 temp; - - fg_read(chip, &exp_sts, + rc = fg_read(chip, &exp_sts, chip->mem_base + MEM_INTF_IMA_EXP_STS, 1); - fg_read(chip, &hw_sts, + if (rc) { + pr_err("Error in reading IMA_EXP_STS, rc=%d\n", rc); + return rc; + } + + rc = fg_read(chip, &hw_sts, chip->mem_base + MEM_INTF_IMA_HW_STS, 1); - pr_err("IMA access failed ima_err_sts=%x ima_exp_sts=%x ima_hw_sts=%x\n", - err_sts, exp_sts, hw_sts); - rc = err_sts; - - /* clear the error */ - ret |= fg_masked_write(chip, chip->mem_base + MEM_INTF_IMA_CFG, - IMA_IACS_CLR, IMA_IACS_CLR, 1); - temp = 0x4; - ret |= fg_write(chip, &temp, MEM_INTF_ADDR_LSB(chip) + 1, 1); - temp = 0x0; - ret |= fg_write(chip, &temp, MEM_INTF_WR_DATA0(chip) + 3, 1); - ret |= fg_read(chip, &temp, MEM_INTF_RD_DATA0(chip) + 3, 1); - ret |= fg_masked_write(chip, chip->mem_base + MEM_INTF_IMA_CFG, - IMA_IACS_CLR, 0, 1); + if (rc) { + pr_err("Error in reading IMA_HW_STS, rc=%d\n", rc); + return rc; + } + + pr_info_once("Initial ima_err_sts=%x ima_exp_sts=%x ima_hw_sts=%x\n", + err_sts, exp_sts, hw_sts); + + if (fg_debug_mask & (FG_MEM_DEBUG_READS | FG_MEM_DEBUG_WRITES)) + pr_info("ima_err_sts=%x ima_exp_sts=%x ima_hw_sts=%x\n", + err_sts, exp_sts, hw_sts); + + if (check_hw_sts) { + /* + * Lower nibble should be equal to upper nibble before SRAM + * transactions begins from SW side. If they are unequal, then + * the error clear sequence should be run irrespective of IMA + * exception errors. + */ + if ((hw_sts & 0x0F) != hw_sts >> 4) { + pr_err("IMA HW not in correct state, hw_sts=%x\n", + hw_sts); + run_err_clr_seq = true; + } + } + + if (exp_sts & (IACS_ERR_BIT | XCT_ERR_BIT | DATA_RD_ERR_BIT | + DATA_WR_ERR_BIT | ADDR_BURST_WRAP_BIT | ADDR_RNG_ERR_BIT | + ADDR_SRC_ERR_BIT)) { + pr_err("IMA exception bit set, exp_sts=%x\n", exp_sts); + run_err_clr_seq = true; + } + + if (run_err_clr_seq) { + ret = fg_run_iacs_clear_sequence(chip); if (!ret) return -EAGAIN; + else + pr_err("Error clearing IMA exception ret=%d\n", ret); + } + + return rc; +} + +static void fg_enable_irqs(struct fg_chip *chip, bool enable) +{ + if (!(enable ^ chip->irqs_enabled)) + return; + + if (enable) { + enable_irq(chip->soc_irq[DELTA_SOC].irq); + enable_irq_wake(chip->soc_irq[DELTA_SOC].irq); + if (!chip->full_soc_irq_enabled) { + enable_irq(chip->soc_irq[FULL_SOC].irq); + enable_irq_wake(chip->soc_irq[FULL_SOC].irq); + chip->full_soc_irq_enabled = true; + } + enable_irq(chip->batt_irq[BATT_MISSING].irq); + if (!chip->vbat_low_irq_enabled) { + enable_irq(chip->batt_irq[VBATT_LOW].irq); + enable_irq_wake(chip->batt_irq[VBATT_LOW].irq); + chip->vbat_low_irq_enabled = true; + } + if (!chip->use_vbat_low_empty_soc) { + enable_irq(chip->soc_irq[EMPTY_SOC].irq); + enable_irq_wake(chip->soc_irq[EMPTY_SOC].irq); + } + chip->irqs_enabled = true; + } else { + disable_irq_wake(chip->soc_irq[DELTA_SOC].irq); + disable_irq_nosync(chip->soc_irq[DELTA_SOC].irq); + if (chip->full_soc_irq_enabled) { + disable_irq_wake(chip->soc_irq[FULL_SOC].irq); + disable_irq_nosync(chip->soc_irq[FULL_SOC].irq); + chip->full_soc_irq_enabled = false; + } + disable_irq(chip->batt_irq[BATT_MISSING].irq); + if (chip->vbat_low_irq_enabled) { + disable_irq_wake(chip->batt_irq[VBATT_LOW].irq); + disable_irq_nosync(chip->batt_irq[VBATT_LOW].irq); + chip->vbat_low_irq_enabled = false; + } + if (!chip->use_vbat_low_empty_soc) { + disable_irq_wake(chip->soc_irq[EMPTY_SOC].irq); + disable_irq_nosync(chip->soc_irq[EMPTY_SOC].irq); + } + chip->irqs_enabled = false; + } + + if (fg_debug_mask & FG_STATUS) + pr_info("FG interrupts are %sabled\n", enable ? "en" : "dis"); +} - pr_err("Error clearing IMA exception ret=%d\n", ret); +static void fg_check_ima_error_handling(struct fg_chip *chip) +{ + if (chip->ima_error_handling) { + if (fg_debug_mask & FG_STATUS) + pr_info("IMA error is handled already!\n"); + return; } + mutex_lock(&chip->ima_recovery_lock); + fg_enable_irqs(chip, false); + chip->use_last_cc_soc = true; + chip->ima_error_handling = true; + if (!work_pending(&chip->ima_error_recovery_work)) + schedule_work(&chip->ima_error_recovery_work); + mutex_unlock(&chip->ima_recovery_lock); +} + +#define SOC_ALG_ST 0xCF +#define FGXCT_PRD BIT(7) +#define ALG_ST_CHECK_COUNT 20 +static int fg_check_alg_status(struct fg_chip *chip) +{ + int rc = 0, timeout = ALG_ST_CHECK_COUNT, count = 0; + u8 ima_opr_sts, alg_sts = 0, temp = 0; + if (!fg_reset_on_lockup) { + pr_info("FG lockup detection cannot be run\n"); + return 0; + } + + rc = fg_read(chip, &alg_sts, chip->soc_base + SOC_ALG_ST, 1); + if (rc) { + pr_err("Error in reading SOC_ALG_ST, rc=%d\n", rc); + return rc; + } + + while (1) { + rc = fg_read(chip, &ima_opr_sts, + chip->mem_base + MEM_INTF_IMA_OPR_STS, 1); + if (!rc && !(ima_opr_sts & FGXCT_PRD)) + break; + + if (rc) { + pr_err("Error in reading IMA_OPR_STS, rc=%d\n", + rc); + break; + } + + rc = fg_read(chip, &temp, chip->soc_base + SOC_ALG_ST, + 1); + if (rc) { + pr_err("Error in reading SOC_ALG_ST, rc=%d\n", + rc); + break; + } + + if ((ima_opr_sts & FGXCT_PRD) && (temp == alg_sts)) + count++; + + /* Wait for ~10ms while polling ALG_ST & IMA_OPR_STS */ + usleep_range(9000, 11000); + + if (!(--timeout)) + break; + } + + if (fg_debug_mask & (FG_MEM_DEBUG_READS | FG_MEM_DEBUG_WRITES)) + pr_info("ima_opr_sts: %x alg_sts: %x count=%d\n", ima_opr_sts, + alg_sts, count); + + if (count == ALG_ST_CHECK_COUNT) { + /* If we are here, that means FG ALG is stuck */ + pr_err("ALG is stuck\n"); + fg_check_ima_error_handling(chip); + rc = -EBUSY; + } return rc; } @@ -1122,19 +1491,25 @@ static int fg_check_iacs_ready(struct fg_chip *chip) while (1) { rc = fg_read(chip, &ima_opr_sts, chip->mem_base + MEM_INTF_IMA_OPR_STS, 1); - if (!rc && (ima_opr_sts & IMA_IACS_RDY)) + if (!rc && (ima_opr_sts & IMA_IACS_RDY)) { break; + } else { + if (!(--timeout) || rc) + break; - if (!(--timeout) || rc) - break; - /* delay for iacs_ready to be asserted */ - usleep_range(5000, 7000); + /* delay for iacs_ready to be asserted */ + usleep_range(5000, 7000); + } } if (!timeout || rc) { - pr_err("IACS_RDY not set\n"); + pr_err("IACS_RDY not set, ima_opr_sts: %x\n", ima_opr_sts); + rc = fg_check_alg_status(chip); + if (rc && rc != -EBUSY) + pr_err("Couldn't check FG ALG status, rc=%d\n", + rc); /* perform IACS_CLR sequence */ - fg_check_ima_exception(chip); + fg_check_ima_exception(chip, false); return -EBUSY; } @@ -1154,15 +1529,16 @@ static int __fg_interleaved_mem_write(struct fg_chip *chip, u8 *val, while (len > 0) { num_bytes = (offset + len) > BUF_LEN ? - (BUF_LEN - offset) : len; + (BUF_LEN - offset) : len; /* write to byte_enable */ for (i = offset; i < (offset + num_bytes); i++) byte_enable |= BIT(i); rc = fg_write(chip, &byte_enable, - chip->mem_base + MEM_INTF_IMA_BYTE_EN, 1); + chip->mem_base + MEM_INTF_IMA_BYTE_EN, 1); if (rc) { - pr_err("Unable to write to byte_en_reg rc=%d\n", rc); + pr_err("Unable to write to byte_en_reg rc=%d\n", + rc); return rc; } /* write data */ @@ -1193,12 +1569,13 @@ static int __fg_interleaved_mem_write(struct fg_chip *chip, u8 *val, rc = fg_check_iacs_ready(chip); if (rc) { - pr_debug("IACS_RDY failed rc=%d\n", rc); + pr_err("IACS_RDY failed post write to address %x offset %d rc=%d\n", + address, offset, rc); return rc; } /* check for error condition */ - rc = fg_check_ima_exception(chip); + rc = fg_check_ima_exception(chip, false); if (rc) { pr_err("IMA transaction failed rc=%d", rc); return rc; @@ -1239,12 +1616,13 @@ static int __fg_interleaved_mem_read(struct fg_chip *chip, u8 *val, u16 address, rc = fg_check_iacs_ready(chip); if (rc) { - pr_debug("IACS_RDY failed rc=%d\n", rc); + pr_err("IACS_RDY failed post read for address %x offset %d rc=%d\n", + address, offset, rc); return rc; } /* check for error condition */ - rc = fg_check_ima_exception(chip); + rc = fg_check_ima_exception(chip, false); if (rc) { pr_err("IMA transaction failed rc=%d", rc); return rc; @@ -1296,7 +1674,7 @@ static int fg_interleaved_mem_config(struct fg_chip *chip, u8 *val, * clear, then return an error instead of waiting for it again. */ if (time_count > 4) { - pr_err("Waited for 1.5 seconds polling RIF_MEM_ACCESS_REQ\n"); + pr_err("Waited for ~16ms polling RIF_MEM_ACCESS_REQ\n"); return -ETIMEDOUT; } @@ -1322,7 +1700,8 @@ static int fg_interleaved_mem_config(struct fg_chip *chip, u8 *val, rc = fg_check_iacs_ready(chip); if (rc) { - pr_debug("IACS_RDY failed rc=%d\n", rc); + pr_err("IACS_RDY failed before setting address: %x offset: %d rc=%d\n", + address, offset, rc); return rc; } @@ -1335,7 +1714,8 @@ static int fg_interleaved_mem_config(struct fg_chip *chip, u8 *val, rc = fg_check_iacs_ready(chip); if (rc) - pr_debug("IACS_RDY failed rc=%d\n", rc); + pr_err("IACS_RDY failed after setting address: %x offset: %d rc=%d\n", + address, offset, rc); return rc; } @@ -1346,10 +1726,13 @@ static int fg_interleaved_mem_config(struct fg_chip *chip, u8 *val, static int fg_interleaved_mem_read(struct fg_chip *chip, u8 *val, u16 address, int len, int offset) { - int rc = 0, orig_address = address; + int rc = 0, ret, orig_address = address; u8 start_beat_count, end_beat_count, count = 0; bool retry = false; + if (chip->fg_shutdown) + return -EINVAL; + if (offset > 3) { pr_err("offset too large %d\n", offset); return -EINVAL; @@ -1372,11 +1755,22 @@ static int fg_interleaved_mem_read(struct fg_chip *chip, u8 *val, u16 address, } mutex_lock(&chip->rw_lock); + if (fg_debug_mask & FG_MEM_DEBUG_READS) + pr_info("Read for %d bytes is attempted @ 0x%x[%d]\n", + len, address, offset); retry: + if (count >= RETRY_COUNT) { + pr_err("Retried reading 3 times\n"); + retry = false; + goto out; + } + rc = fg_interleaved_mem_config(chip, val, address, offset, len, 0); if (rc) { pr_err("failed to configure SRAM for IMA rc = %d\n", rc); + retry = true; + count++; goto out; } @@ -1385,18 +1779,21 @@ retry: chip->mem_base + MEM_INTF_FG_BEAT_COUNT, 1); if (rc) { pr_err("failed to read beat count rc=%d\n", rc); + retry = true; + count++; goto out; } /* read data */ rc = __fg_interleaved_mem_read(chip, val, address, offset, len); if (rc) { + count++; if ((rc == -EAGAIN) && (count < RETRY_COUNT)) { - count++; pr_err("IMA access failed retry_count = %d\n", count); goto retry; } else { pr_err("failed to read SRAM address rc = %d\n", rc); + retry = true; goto out; } } @@ -1406,6 +1803,8 @@ retry: chip->mem_base + MEM_INTF_FG_BEAT_COUNT, 1); if (rc) { pr_err("failed to read beat count rc=%d\n", rc); + retry = true; + count++; goto out; } @@ -1418,12 +1817,13 @@ retry: if (fg_debug_mask & FG_MEM_DEBUG_READS) pr_info("Beat count do not match - retry transaction\n"); retry = true; + count++; } out: /* Release IMA access */ - rc = fg_masked_write(chip, MEM_INTF_CFG(chip), IMA_REQ_ACCESS, 0, 1); - if (rc) - pr_err("failed to reset IMA access bit rc = %d\n", rc); + ret = fg_masked_write(chip, MEM_INTF_CFG(chip), IMA_REQ_ACCESS, 0, 1); + if (ret) + pr_err("failed to reset IMA access bit ret = %d\n", ret); if (retry) { retry = false; @@ -1439,8 +1839,12 @@ exit: static int fg_interleaved_mem_write(struct fg_chip *chip, u8 *val, u16 address, int len, int offset) { - int rc = 0, orig_address = address; + int rc = 0, ret, orig_address = address; u8 count = 0; + bool retry = false; + + if (chip->fg_shutdown) + return -EINVAL; if (address < RAM_OFFSET) return -EINVAL; @@ -1455,32 +1859,49 @@ static int fg_interleaved_mem_write(struct fg_chip *chip, u8 *val, u16 address, offset = (orig_address + offset) % 4; mutex_lock(&chip->rw_lock); + if (fg_debug_mask & FG_MEM_DEBUG_WRITES) + pr_info("Write for %d bytes is attempted @ 0x%x[%d]\n", + len, address, offset); retry: + if (count >= RETRY_COUNT) { + pr_err("Retried writing 3 times\n"); + retry = false; + goto out; + } + rc = fg_interleaved_mem_config(chip, val, address, offset, len, 1); if (rc) { - pr_err("failed to xonfigure SRAM for IMA rc = %d\n", rc); + pr_err("failed to configure SRAM for IMA rc = %d\n", rc); + retry = true; + count++; goto out; } /* write data */ rc = __fg_interleaved_mem_write(chip, val, address, offset, len); if (rc) { + count++; if ((rc == -EAGAIN) && (count < RETRY_COUNT)) { - count++; pr_err("IMA access failed retry_count = %d\n", count); goto retry; } else { pr_err("failed to write SRAM address rc = %d\n", rc); + retry = true; goto out; } } out: /* Release IMA access */ - rc = fg_masked_write(chip, MEM_INTF_CFG(chip), IMA_REQ_ACCESS, 0, 1); - if (rc) - pr_err("failed to reset IMA access bit rc = %d\n", rc); + ret = fg_masked_write(chip, MEM_INTF_CFG(chip), IMA_REQ_ACCESS, 0, 1); + if (ret) + pr_err("failed to reset IMA access bit ret = %d\n", ret); + + if (retry) { + retry = false; + goto retry; + } mutex_unlock(&chip->rw_lock); fg_relax(&chip->memif_wakeup_source); @@ -1490,6 +1911,9 @@ out: static int fg_mem_read(struct fg_chip *chip, u8 *val, u16 address, int len, int offset, bool keep_access) { + if (chip->block_sram_access) + return -EBUSY; + if (chip->ima_supported) return fg_interleaved_mem_read(chip, val, address, len, offset); @@ -1501,6 +1925,9 @@ static int fg_mem_read(struct fg_chip *chip, u8 *val, u16 address, static int fg_mem_write(struct fg_chip *chip, u8 *val, u16 address, int len, int offset, bool keep_access) { + if (chip->block_sram_access) + return -EBUSY; + if (chip->ima_supported) return fg_interleaved_mem_write(chip, val, address, len, offset); @@ -1538,6 +1965,62 @@ static int fg_mem_masked_write(struct fg_chip *chip, u16 addr, return rc; } +static u8 sram_backup_buffer[100]; +static int fg_backup_sram_registers(struct fg_chip *chip, bool save) +{ + int rc, i, len, offset; + u16 address; + u8 *ptr; + + if (fg_debug_mask & FG_STATUS) + pr_info("%sing SRAM registers\n", save ? "Back" : "Restor"); + + ptr = sram_backup_buffer; + for (i = 0; i < FG_BACKUP_MAX; i++) { + address = fg_backup_regs[i].address; + offset = fg_backup_regs[i].offset; + len = fg_backup_regs[i].len; + if (save) + rc = fg_interleaved_mem_read(chip, ptr, address, + len, offset); + else + rc = fg_interleaved_mem_write(chip, ptr, address, + len, offset); + if (rc) { + pr_err("Error in reading %d bytes from %x[%d], rc=%d\n", + len, address, offset, rc); + break; + } + ptr += len; + } + + return rc; +} + +#define SOC_FG_RESET 0xF3 +#define RESET_MASK (BIT(7) | BIT(5)) +static int fg_reset(struct fg_chip *chip, bool reset) +{ + int rc; + + rc = fg_sec_masked_write(chip, chip->soc_base + SOC_FG_RESET, + 0xFF, reset ? RESET_MASK : 0, 1); + if (rc) + pr_err("Error in writing to 0x%x, rc=%d\n", SOC_FG_RESET, rc); + + return rc; +} + +static void fg_handle_battery_insertion(struct fg_chip *chip) +{ + reinit_completion(&chip->batt_id_avail); + reinit_completion(&chip->fg_reset_done); + schedule_delayed_work(&chip->batt_profile_init, 0); + cancel_delayed_work(&chip->update_sram_data); + schedule_delayed_work(&chip->update_sram_data, msecs_to_jiffies(0)); +} + + static int soc_to_setpoint(int soc) { return DIV_ROUND_CLOSEST(soc * 255, 100); @@ -1550,6 +2033,7 @@ static void batt_to_setpoint_adc(int vbatt_mv, u8 *data) val = DIV_ROUND_CLOSEST(vbatt_mv * 32768, 5000); data[0] = val & 0xFF; data[1] = val >> 8; + return; } static u8 batt_to_setpoint_8b(int vbatt_mv) @@ -1678,14 +2162,37 @@ out: return rc; } +#define VBATT_LOW_STS_BIT BIT(2) +static int fg_get_vbatt_status(struct fg_chip *chip, bool *vbatt_low_sts) +{ + int rc = 0; + u8 fg_batt_sts; + + rc = fg_read(chip, &fg_batt_sts, INT_RT_STS(chip->batt_base), 1); + if (rc) + pr_err("spmi read failed: addr=%03X, rc=%d\n", + INT_RT_STS(chip->batt_base), rc); + else + *vbatt_low_sts = !!(fg_batt_sts & VBATT_LOW_STS_BIT); + + return rc; +} + #define SOC_EMPTY BIT(3) static bool fg_is_batt_empty(struct fg_chip *chip) { u8 fg_soc_sts; int rc; + bool vbatt_low_sts; - rc = fg_read(chip, &fg_soc_sts, - INT_RT_STS(chip->soc_base), 1); + if (chip->use_vbat_low_empty_soc) { + if (fg_get_vbatt_status(chip, &vbatt_low_sts)) + return false; + + return vbatt_low_sts; + } + + rc = fg_read(chip, &fg_soc_sts, INT_RT_STS(chip->soc_base), 1); if (rc) { pr_err("spmi read failed: addr=%03X, rc=%d\n", INT_RT_STS(chip->soc_base), rc); @@ -1732,7 +2239,16 @@ static int get_monotonic_soc_raw(struct fg_chip *chip) #define FULL_SOC_RAW 0xFF static int get_prop_capacity(struct fg_chip *chip) { - int msoc; + int msoc, rc; + bool vbatt_low_sts; + + if (chip->use_last_soc && chip->last_soc) { + if (chip->last_soc == FULL_SOC_RAW) + return FULL_CAPACITY; + return DIV_ROUND_CLOSEST((chip->last_soc - 1) * + (FULL_CAPACITY - 2), + FULL_SOC_RAW - 2) + 1; + } if (chip->battery_missing) return MISSING_CAPACITY; @@ -1747,10 +2263,28 @@ static int get_prop_capacity(struct fg_chip *chip) return EMPTY_CAPACITY; } msoc = get_monotonic_soc_raw(chip); - if (msoc == 0) - return EMPTY_CAPACITY; - else if (msoc == FULL_SOC_RAW) + if (msoc == 0) { + if (fg_reset_on_lockup && chip->use_vbat_low_empty_soc) { + rc = fg_get_vbatt_status(chip, &vbatt_low_sts); + if (rc) { + pr_err("Error in reading vbatt_status, rc=%d\n", + rc); + return EMPTY_CAPACITY; + } + + if (!vbatt_low_sts) + return DIV_ROUND_CLOSEST((chip->last_soc - 1) * + (FULL_CAPACITY - 2), + FULL_SOC_RAW - 2) + 1; + else + return EMPTY_CAPACITY; + } else { + return EMPTY_CAPACITY; + } + } else if (msoc == FULL_SOC_RAW) { return FULL_CAPACITY; + } + return DIV_ROUND_CLOSEST((msoc - 1) * (FULL_CAPACITY - 2), FULL_SOC_RAW - 2) + 1; } @@ -1843,6 +2377,25 @@ static int set_prop_sense_type(struct fg_chip *chip, int ext_sense_type) return 0; } +#define IGNORE_FALSE_NEGATIVE_ISENSE_BIT BIT(3) +static int set_prop_ignore_false_negative_isense(struct fg_chip *chip, + bool ignore) +{ + int rc; + + rc = fg_mem_masked_write(chip, EXTERNAL_SENSE_SELECT, + IGNORE_FALSE_NEGATIVE_ISENSE_BIT, + ignore ? IGNORE_FALSE_NEGATIVE_ISENSE_BIT : 0, + EXTERNAL_SENSE_OFFSET); + if (rc) { + pr_err("failed to %s isense false negative ignore rc=%d\n", + ignore ? "enable" : "disable", rc); + return rc; + } + + return 0; +} + #define EXPONENT_MASK 0xF800 #define MANTISSA_MASK 0x3FF #define SIGN BIT(10) @@ -1953,8 +2506,7 @@ static int fg_is_batt_id_valid(struct fg_chip *chip) return rc; } - if (fg_debug_mask & FG_IRQS) - pr_info("fg batt sts 0x%x\n", fg_batt_sts); + pr_debug("fg batt sts 0x%x\n", fg_batt_sts); return (fg_batt_sts & BATT_IDED) ? 1 : 0; } @@ -1984,7 +2536,7 @@ static int64_t twos_compliment_extend(int64_t val, int nbytes) #define DECIKELVIN 2730 #define SRAM_PERIOD_NO_ID_UPDATE_MS 100 #define FULL_PERCENT_28BIT 0xFFFFFFF -static void update_sram_data(struct fg_chip *chip, int *resched_ms) +static int update_sram_data(struct fg_chip *chip, int *resched_ms) { int i, j, rc = 0; u8 reg[4]; @@ -2060,6 +2612,31 @@ static void update_sram_data(struct fg_chip *chip, int *resched_ms) } fg_mem_release(chip); + /* Backup the registers whenever no error happens during update */ + if (fg_reset_on_lockup && !chip->ima_error_handling) { + if (!rc) { + if (fg_debug_mask & FG_STATUS) + pr_info("backing up SRAM registers\n"); + rc = fg_backup_sram_registers(chip, true); + if (rc) { + pr_err("Couldn't save sram registers\n"); + goto out; + } + if (!chip->use_last_soc) { + chip->last_soc = get_monotonic_soc_raw(chip); + chip->last_cc_soc = div64_s64( + (int64_t)chip->last_soc * + FULL_PERCENT_28BIT, FULL_SOC_RAW); + } + if (fg_debug_mask & FG_STATUS) + pr_info("last_soc: %d last_cc_soc: %lld\n", + chip->last_soc, chip->last_cc_soc); + } else { + pr_err("update_sram failed\n"); + goto out; + } + } + if (!rc) get_current_time(&chip->last_sram_update_time); @@ -2070,7 +2647,55 @@ resched: } else { *resched_ms = SRAM_PERIOD_NO_ID_UPDATE_MS; } +out: fg_relax(&chip->update_sram_wakeup_source); + return rc; +} + +#define SANITY_CHECK_PERIOD_MS 5000 +static void check_sanity_work(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, + struct fg_chip, + check_sanity_work.work); + int rc = 0; + u8 beat_count; + bool tried_once = false; + + fg_stay_awake(&chip->sanity_wakeup_source); + +try_again: + rc = fg_read(chip, &beat_count, + chip->mem_base + MEM_INTF_FG_BEAT_COUNT, 1); + if (rc) { + pr_err("failed to read beat count rc=%d\n", rc); + goto resched; + } + + if (fg_debug_mask & FG_STATUS) + pr_info("current: %d, prev: %d\n", beat_count, + chip->last_beat_count); + + if (chip->last_beat_count == beat_count) { + if (!tried_once) { + /* Wait for 1 FG cycle and read it once again */ + msleep(1500); + tried_once = true; + goto try_again; + } else { + pr_err("Beat count not updating\n"); + fg_check_ima_error_handling(chip); + goto out; + } + } else { + chip->last_beat_count = beat_count; + } +resched: + schedule_delayed_work( + &chip->check_sanity_work, + msecs_to_jiffies(SANITY_CHECK_PERIOD_MS)); +out: + fg_relax(&chip->sanity_wakeup_source); } #define SRAM_TIMEOUT_MS 3000 @@ -2079,8 +2704,9 @@ static void update_sram_data_work(struct work_struct *work) struct fg_chip *chip = container_of(work, struct fg_chip, update_sram_data.work); - int resched_ms = SRAM_PERIOD_NO_ID_UPDATE_MS, ret; + int resched_ms, ret; bool tried_again = false; + int rc = 0; wait: /* Wait for MEMIF access revoked */ @@ -2094,14 +2720,19 @@ wait: goto wait; } else if (ret <= 0) { pr_err("transaction timed out ret=%d\n", ret); + if (fg_is_batt_id_valid(chip)) + resched_ms = fg_sram_update_period_ms; + else + resched_ms = SRAM_PERIOD_NO_ID_UPDATE_MS; goto out; } - update_sram_data(chip, &resched_ms); + rc = update_sram_data(chip, &resched_ms); out: - schedule_delayed_work( - &chip->update_sram_data, - msecs_to_jiffies(resched_ms)); + if (!rc) + schedule_delayed_work( + &chip->update_sram_data, + msecs_to_jiffies(resched_ms)); } #define BATT_TEMP_OFFSET 3 @@ -2115,6 +2746,8 @@ out: TEMP_SENSE_CHARGE_BIT) #define TEMP_PERIOD_UPDATE_MS 10000 #define TEMP_PERIOD_TIMEOUT_MS 3000 +#define BATT_TEMP_LOW_LIMIT -600 +#define BATT_TEMP_HIGH_LIMIT 1500 static void update_temp_data(struct work_struct *work) { s16 temp; @@ -2166,14 +2799,44 @@ wait: } temp = reg[0] | (reg[1] << 8); - fg_data[0].value = (temp * TEMP_LSB_16B / 1000) - - DECIKELVIN; + temp = (temp * TEMP_LSB_16B / 1000) - DECIKELVIN; + + /* + * If temperature is within the specified range (e.g. -60C and 150C), + * update it to the userspace. Otherwise, use the last read good + * temperature. + */ + if (temp > chip->batt_temp_low_limit && + temp < chip->batt_temp_high_limit) { + chip->last_good_temp = temp; + fg_data[0].value = temp; + } else { + fg_data[0].value = chip->last_good_temp; + + /* + * If the temperature is read before and seems to be in valid + * range, then a bad temperature reading could be because of + * FG lockup. Trigger the FG reset sequence in such cases. + */ + if (chip->last_temp_update_time && fg_reset_on_lockup && + (chip->last_good_temp > chip->batt_temp_low_limit && + chip->last_good_temp < chip->batt_temp_high_limit)) { + pr_err("Batt_temp is %d !, triggering FG reset\n", + temp); + fg_check_ima_error_handling(chip); + } + } if (fg_debug_mask & FG_MEM_DEBUG_READS) pr_info("BATT_TEMP %d %d\n", temp, fg_data[0].value); get_current_time(&chip->last_temp_update_time); + if (chip->soc_slope_limiter_en) { + fg_stay_awake(&chip->slope_limit_wakeup_source); + schedule_work(&chip->slope_limiter_work); + } + out: if (chip->sw_rbias_ctrl) { rc = fg_mem_masked_write(chip, EXTERNAL_SENSE_SELECT, @@ -2226,18 +2889,6 @@ static int fg_set_resume_soc(struct fg_chip *chip, u8 threshold) return rc; } -#define VBATT_LOW_STS_BIT BIT(2) -static int fg_get_vbatt_status(struct fg_chip *chip, bool *vbatt_low_sts) -{ - int rc = 0; - u8 fg_batt_sts; - - rc = fg_read(chip, &fg_batt_sts, INT_RT_STS(chip->batt_base), 1); - if (!rc) - *vbatt_low_sts = !!(fg_batt_sts & VBATT_LOW_STS_BIT); - return rc; -} - #define BATT_CYCLE_NUMBER_REG 0x5E8 #define BATT_CYCLE_OFFSET 0 static void restore_cycle_counter(struct fg_chip *chip) @@ -2301,6 +2952,9 @@ static int fg_inc_store_cycle_ctr(struct fg_chip *chip, int bucket) bucket, rc); else chip->cyc_ctr.count[bucket] = cyc_count; + + if (fg_debug_mask & FG_POWER_SUPPLY) + pr_info("Stored bucket %d cyc_count: %d\n", bucket, cyc_count); return rc; } @@ -2416,6 +3070,62 @@ static int bcap_uah_2b(u8 *buffer) return ((int)val) * 1000; } +#define SLOPE_LIMITER_COEFF_REG 0x430 +#define SLOPE_LIMITER_COEFF_OFFSET 3 +#define SLOPE_LIMIT_TEMP_THRESHOLD 100 +#define SLOPE_LIMIT_LOW_TEMP_CHG 45 +#define SLOPE_LIMIT_HIGH_TEMP_CHG 2 +#define SLOPE_LIMIT_LOW_TEMP_DISCHG 45 +#define SLOPE_LIMIT_HIGH_TEMP_DISCHG 2 +static void slope_limiter_work(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, struct fg_chip, + slope_limiter_work); + enum slope_limit_status status; + int batt_temp, rc; + u8 buf[2]; + int64_t val; + + batt_temp = get_sram_prop_now(chip, FG_DATA_BATT_TEMP); + + if (chip->status == POWER_SUPPLY_STATUS_CHARGING || + chip->status == POWER_SUPPLY_STATUS_FULL) { + if (batt_temp < chip->slope_limit_temp) + status = LOW_TEMP_CHARGE; + else + status = HIGH_TEMP_CHARGE; + } else if (chip->status == POWER_SUPPLY_STATUS_DISCHARGING) { + if (batt_temp < chip->slope_limit_temp) + status = LOW_TEMP_DISCHARGE; + else + status = HIGH_TEMP_DISCHARGE; + } else { + goto out; + } + + if (status == chip->slope_limit_sts) + goto out; + + val = chip->slope_limit_coeffs[status]; + val *= MICRO_UNIT; + half_float_to_buffer(val, buf); + rc = fg_mem_write(chip, buf, + SLOPE_LIMITER_COEFF_REG, 2, + SLOPE_LIMITER_COEFF_OFFSET, 0); + if (rc) { + pr_err("Couldn't write to slope_limiter_coeff_reg, rc=%d\n", + rc); + goto out; + } + + chip->slope_limit_sts = status; + if (fg_debug_mask & FG_STATUS) + pr_info("Slope limit sts: %d val: %lld buf[%x %x] written\n", + status, val, buf[0], buf[1]); +out: + fg_relax(&chip->slope_limit_wakeup_source); +} + static int lookup_ocv_for_soc(struct fg_chip *chip, int soc) { int64_t *coeffs; @@ -2481,6 +3191,7 @@ static int lookup_soc_for_ocv(struct fg_chip *chip, int ocv) #define ESR_ACTUAL_REG 0x554 #define BATTERY_ESR_REG 0x4F4 #define TEMP_RS_TO_RSLOW_REG 0x514 +#define ESR_OFFSET 2 static int estimate_battery_age(struct fg_chip *chip, int *actual_capacity) { int64_t ocv_cutoff_new, ocv_cutoff_aged, temp_rs_to_rslow; @@ -2519,7 +3230,7 @@ static int estimate_battery_age(struct fg_chip *chip, int *actual_capacity) rc = fg_mem_read(chip, buffer, ESR_ACTUAL_REG, 2, 2, 0); esr_actual = half_float(buffer); - rc |= fg_mem_read(chip, buffer, BATTERY_ESR_REG, 2, 2, 0); + rc |= fg_mem_read(chip, buffer, BATTERY_ESR_REG, 2, ESR_OFFSET, 0); battery_esr = half_float(buffer); if (rc) { @@ -2594,124 +3305,6 @@ static void battery_age_work(struct work_struct *work) estimate_battery_age(chip, &chip->actual_cap_uah); } -static enum power_supply_property fg_power_props[] = { - POWER_SUPPLY_PROP_CAPACITY, - POWER_SUPPLY_PROP_CAPACITY_RAW, - POWER_SUPPLY_PROP_CURRENT_NOW, - POWER_SUPPLY_PROP_VOLTAGE_NOW, - POWER_SUPPLY_PROP_VOLTAGE_OCV, - POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, - POWER_SUPPLY_PROP_CHARGE_NOW, - POWER_SUPPLY_PROP_CHARGE_NOW_RAW, - POWER_SUPPLY_PROP_CHARGE_NOW_ERROR, - POWER_SUPPLY_PROP_CHARGE_FULL, - POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, - POWER_SUPPLY_PROP_TEMP, - POWER_SUPPLY_PROP_COOL_TEMP, - POWER_SUPPLY_PROP_WARM_TEMP, - POWER_SUPPLY_PROP_RESISTANCE, - POWER_SUPPLY_PROP_RESISTANCE_ID, - POWER_SUPPLY_PROP_BATTERY_TYPE, - POWER_SUPPLY_PROP_UPDATE_NOW, - POWER_SUPPLY_PROP_ESR_COUNT, - POWER_SUPPLY_PROP_VOLTAGE_MIN, - POWER_SUPPLY_PROP_CYCLE_COUNT, - POWER_SUPPLY_PROP_CYCLE_COUNT_ID, - POWER_SUPPLY_PROP_HI_POWER, -}; - -static int fg_power_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) -{ - struct fg_chip *chip = power_supply_get_drvdata(psy); - bool vbatt_low_sts; - - switch (psp) { - case POWER_SUPPLY_PROP_BATTERY_TYPE: - if (chip->battery_missing) - val->strval = missing_batt_type; - else if (chip->fg_restarting) - val->strval = loading_batt_type; - else - val->strval = chip->batt_type; - break; - case POWER_SUPPLY_PROP_CAPACITY: - val->intval = get_prop_capacity(chip); - break; - case POWER_SUPPLY_PROP_CAPACITY_RAW: - val->intval = get_sram_prop_now(chip, FG_DATA_BATT_SOC); - break; - case POWER_SUPPLY_PROP_CHARGE_NOW_ERROR: - val->intval = get_sram_prop_now(chip, FG_DATA_VINT_ERR); - break; - case POWER_SUPPLY_PROP_CURRENT_NOW: - val->intval = get_sram_prop_now(chip, FG_DATA_CURRENT); - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = get_sram_prop_now(chip, FG_DATA_VOLTAGE); - break; - case POWER_SUPPLY_PROP_VOLTAGE_OCV: - val->intval = get_sram_prop_now(chip, FG_DATA_OCV); - break; - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: - val->intval = chip->batt_max_voltage_uv; - break; - case POWER_SUPPLY_PROP_TEMP: - val->intval = get_sram_prop_now(chip, FG_DATA_BATT_TEMP); - break; - case POWER_SUPPLY_PROP_COOL_TEMP: - val->intval = get_prop_jeita_temp(chip, FG_MEM_SOFT_COLD); - break; - case POWER_SUPPLY_PROP_WARM_TEMP: - val->intval = get_prop_jeita_temp(chip, FG_MEM_SOFT_HOT); - break; - case POWER_SUPPLY_PROP_RESISTANCE: - val->intval = get_sram_prop_now(chip, FG_DATA_BATT_ESR); - break; - case POWER_SUPPLY_PROP_ESR_COUNT: - val->intval = get_sram_prop_now(chip, FG_DATA_BATT_ESR_COUNT); - break; - case POWER_SUPPLY_PROP_CYCLE_COUNT: - val->intval = fg_get_cycle_count(chip); - break; - case POWER_SUPPLY_PROP_CYCLE_COUNT_ID: - val->intval = chip->cyc_ctr.id; - break; - case POWER_SUPPLY_PROP_RESISTANCE_ID: - val->intval = get_sram_prop_now(chip, FG_DATA_BATT_ID); - break; - case POWER_SUPPLY_PROP_UPDATE_NOW: - val->intval = 0; - break; - case POWER_SUPPLY_PROP_VOLTAGE_MIN: - if (!fg_get_vbatt_status(chip, &vbatt_low_sts)) - val->intval = (int)vbatt_low_sts; - else - val->intval = 1; - break; - case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: - val->intval = chip->nom_cap_uah; - break; - case POWER_SUPPLY_PROP_CHARGE_FULL: - val->intval = chip->learning_data.learned_cc_uah; - break; - case POWER_SUPPLY_PROP_CHARGE_NOW: - val->intval = chip->learning_data.cc_uah; - break; - case POWER_SUPPLY_PROP_CHARGE_NOW_RAW: - val->intval = get_sram_prop_now(chip, FG_DATA_CC_CHARGE); - break; - case POWER_SUPPLY_PROP_HI_POWER: - val->intval = !!chip->bcl_lpm_disabled; - break; - default: - return -EINVAL; - } - - return 0; -} - static int correction_times[] = { 1470, 2940, @@ -2853,11 +3446,8 @@ static void fg_cap_learning_work(struct work_struct *work) goto fail; } - if (chip->wa_flag & USE_CC_SOC_REG) { - mutex_unlock(&chip->learning_data.learning_lock); - fg_relax(&chip->capacity_learning_wakeup_source); - return; - } + if (chip->wa_flag & USE_CC_SOC_REG) + goto fail; fg_mem_lock(chip); @@ -2888,6 +3478,8 @@ static void fg_cap_learning_work(struct work_struct *work) pr_info("total_cc_uah = %lld\n", chip->learning_data.cc_uah); fail: + if (chip->wa_flag & USE_CC_SOC_REG) + fg_relax(&chip->capacity_learning_wakeup_source); mutex_unlock(&chip->learning_data.learning_lock); return; @@ -2901,7 +3493,7 @@ static int fg_get_cc_soc(struct fg_chip *chip, int *cc_soc) { int rc; u8 reg[4]; - unsigned int temp, magnitude; + int temp; rc = fg_mem_read(chip, reg, CC_SOC_BASE_REG, 4, CC_SOC_OFFSET, 0); if (rc) { @@ -2910,20 +3502,61 @@ static int fg_get_cc_soc(struct fg_chip *chip, int *cc_soc) } temp = reg[3] << 24 | reg[2] << 16 | reg[1] << 8 | reg[0]; - magnitude = temp & CC_SOC_MAGNITUDE_MASK; - if (temp & CC_SOC_NEGATIVE_BIT) - *cc_soc = -1 * (~magnitude + 1); - else - *cc_soc = magnitude; - + *cc_soc = sign_extend32(temp, 29); return 0; } +static int fg_get_current_cc(struct fg_chip *chip) +{ + int cc_soc, rc; + int64_t current_capacity; + + if (!(chip->wa_flag & USE_CC_SOC_REG)) + return chip->learning_data.cc_uah; + + if (!chip->learning_data.learned_cc_uah) + return -EINVAL; + + rc = fg_get_cc_soc(chip, &cc_soc); + if (rc < 0) { + pr_err("Failed to get cc_soc, rc=%d\n", rc); + return rc; + } + + current_capacity = cc_soc * chip->learning_data.learned_cc_uah; + current_capacity = div64_u64(current_capacity, FULL_PERCENT_28BIT); + return current_capacity; +} + +#define BATT_MISSING_STS BIT(6) +static bool is_battery_missing(struct fg_chip *chip) +{ + int rc; + u8 fg_batt_sts; + + rc = fg_read(chip, &fg_batt_sts, + INT_RT_STS(chip->batt_base), 1); + if (rc) { + pr_err("spmi read failed: addr=%03X, rc=%d\n", + INT_RT_STS(chip->batt_base), rc); + return false; + } + + return (fg_batt_sts & BATT_MISSING_STS) ? true : false; +} + static int fg_cap_learning_process_full_data(struct fg_chip *chip) { int cc_pc_val, rc = -EINVAL; unsigned int cc_soc_delta_pc; int64_t delta_cc_uah; + uint64_t temp; + bool batt_missing = is_battery_missing(chip); + + if (batt_missing) { + pr_err("Battery is missing!\n"); + goto fail; + } if (!chip->learning_data.active) goto fail; @@ -2940,9 +3573,8 @@ static int fg_cap_learning_process_full_data(struct fg_chip *chip) goto fail; } - cc_soc_delta_pc = DIV_ROUND_CLOSEST( - abs(cc_pc_val - chip->learning_data.init_cc_pc_val) - * 100, FULL_PERCENT_28BIT); + temp = abs(cc_pc_val - chip->learning_data.init_cc_pc_val); + cc_soc_delta_pc = DIV_ROUND_CLOSEST_ULL(temp * 100, FULL_PERCENT_28BIT); delta_cc_uah = div64_s64( chip->learning_data.learned_cc_uah * cc_soc_delta_pc, @@ -2950,8 +3582,11 @@ static int fg_cap_learning_process_full_data(struct fg_chip *chip) chip->learning_data.cc_uah = delta_cc_uah + chip->learning_data.cc_uah; if (fg_debug_mask & FG_AGING) - pr_info("current cc_soc=%d cc_soc_pc=%d total_cc_uah = %lld\n", + pr_info("current cc_soc=%d cc_soc_pc=%d init_cc_pc_val=%d delta_cc_uah=%lld learned_cc_uah=%lld total_cc_uah = %lld\n", cc_pc_val, cc_soc_delta_pc, + chip->learning_data.init_cc_pc_val, + delta_cc_uah, + chip->learning_data.learned_cc_uah, chip->learning_data.cc_uah); return 0; @@ -3044,6 +3679,12 @@ static void fg_cap_learning_save_data(struct fg_chip *chip) { int16_t cc_mah; int rc; + bool batt_missing = is_battery_missing(chip); + + if (batt_missing) { + pr_err("Battery is missing!\n"); + return; + } cc_mah = div64_s64(chip->learning_data.learned_cc_uah, 1000); @@ -3065,6 +3706,12 @@ static void fg_cap_learning_save_data(struct fg_chip *chip) static void fg_cap_learning_post_process(struct fg_chip *chip) { int64_t max_inc_val, min_dec_val, old_cap; + bool batt_missing = is_battery_missing(chip); + + if (batt_missing) { + pr_err("Battery is missing!\n"); + return; + } max_inc_val = chip->learning_data.learned_cc_uah * (1000 + chip->learning_data.max_increment); @@ -3083,6 +3730,32 @@ static void fg_cap_learning_post_process(struct fg_chip *chip) chip->learning_data.learned_cc_uah = chip->learning_data.cc_uah; + if (chip->learning_data.max_cap_limit) { + max_inc_val = (int64_t)chip->nom_cap_uah * (1000 + + chip->learning_data.max_cap_limit); + max_inc_val = div64_u64(max_inc_val, 1000); + if (chip->learning_data.cc_uah > max_inc_val) { + if (fg_debug_mask & FG_AGING) + pr_info("learning capacity %lld goes above max limit %lld\n", + chip->learning_data.cc_uah, + max_inc_val); + chip->learning_data.learned_cc_uah = max_inc_val; + } + } + + if (chip->learning_data.min_cap_limit) { + min_dec_val = (int64_t)chip->nom_cap_uah * (1000 - + chip->learning_data.min_cap_limit); + min_dec_val = div64_u64(min_dec_val, 1000); + if (chip->learning_data.cc_uah < min_dec_val) { + if (fg_debug_mask & FG_AGING) + pr_info("learning capacity %lld goes below min limit %lld\n", + chip->learning_data.cc_uah, + min_dec_val); + chip->learning_data.learned_cc_uah = min_dec_val; + } + } + fg_cap_learning_save_data(chip); if (fg_debug_mask & FG_AGING) pr_info("final cc_uah = %lld, learned capacity %lld -> %lld uah\n", @@ -3142,7 +3815,7 @@ static int fg_cap_learning_check(struct fg_chip *chip) if (battery_soc * 100 / FULL_PERCENT_3B > chip->learning_data.max_start_soc) { if (fg_debug_mask & FG_AGING) - pr_info("battery soc too low (%d < %d), aborting\n", + pr_info("battery soc too high (%d > %d), aborting\n", battery_soc * 100 / FULL_PERCENT_3B, chip->learning_data.max_start_soc); fg_mem_release(chip); @@ -3226,6 +3899,17 @@ static int fg_cap_learning_check(struct fg_chip *chip) } fg_cap_learning_stop(chip); + } else if (chip->status == POWER_SUPPLY_STATUS_FULL) { + if (chip->wa_flag & USE_CC_SOC_REG) { + /* reset SW_CC_SOC register to 100% upon charge_full */ + rc = fg_mem_write(chip, (u8 *)&cc_pc_100, + CC_SOC_BASE_REG, 4, CC_SOC_OFFSET, 0); + if (rc) + pr_err("Failed to reset CC_SOC_REG rc=%d\n", + rc); + else if (fg_debug_mask & FG_STATUS) + pr_info("Reset SW_CC_SOC to full value\n"); + } } fail: @@ -3323,7 +4007,14 @@ static void status_change_work(struct work_struct *work) struct fg_chip, status_change_work); unsigned long current_time = 0; - int cc_soc, rc, capacity = get_prop_capacity(chip); + int cc_soc, batt_soc, rc, capacity = get_prop_capacity(chip); + bool batt_missing = is_battery_missing(chip); + + if (batt_missing) { + if (fg_debug_mask & FG_STATUS) + pr_info("Battery is missing\n"); + return; + } if (chip->esr_pulse_tune_en) { fg_stay_awake(&chip->esr_extract_wakeup_source); @@ -3343,19 +4034,34 @@ static void status_change_work(struct work_struct *work) } if (chip->status == POWER_SUPPLY_STATUS_FULL || chip->status == POWER_SUPPLY_STATUS_CHARGING) { - if (!chip->vbat_low_irq_enabled) { + if (!chip->vbat_low_irq_enabled && + !chip->use_vbat_low_empty_soc) { enable_irq(chip->batt_irq[VBATT_LOW].irq); enable_irq_wake(chip->batt_irq[VBATT_LOW].irq); chip->vbat_low_irq_enabled = true; } + + if (!chip->full_soc_irq_enabled) { + enable_irq(chip->soc_irq[FULL_SOC].irq); + enable_irq_wake(chip->soc_irq[FULL_SOC].irq); + chip->full_soc_irq_enabled = true; + } + if (!!(chip->wa_flag & PULSE_REQUEST_WA) && capacity == 100) fg_configure_soc(chip); } else if (chip->status == POWER_SUPPLY_STATUS_DISCHARGING) { - if (chip->vbat_low_irq_enabled) { + if (chip->vbat_low_irq_enabled && + !chip->use_vbat_low_empty_soc) { disable_irq_wake(chip->batt_irq[VBATT_LOW].irq); disable_irq_nosync(chip->batt_irq[VBATT_LOW].irq); chip->vbat_low_irq_enabled = false; } + + if (chip->full_soc_irq_enabled) { + disable_irq_wake(chip->soc_irq[FULL_SOC].irq); + disable_irq_nosync(chip->soc_irq[FULL_SOC].irq); + chip->full_soc_irq_enabled = false; + } } fg_cap_learning_check(chip); schedule_work(&chip->update_esr_work); @@ -3368,6 +4074,42 @@ static void status_change_work(struct work_struct *work) } if (chip->prev_status != chip->status && chip->last_sram_update_time) { + /* + * Reset SW_CC_SOC to a value based off battery SOC when + * the device is discharging. + */ + if (chip->status == POWER_SUPPLY_STATUS_DISCHARGING) { + batt_soc = get_battery_soc_raw(chip); + if (!batt_soc) + return; + + batt_soc = div64_s64((int64_t)batt_soc * + FULL_PERCENT_28BIT, FULL_PERCENT_3B); + rc = fg_mem_write(chip, (u8 *)&batt_soc, + CC_SOC_BASE_REG, 4, CC_SOC_OFFSET, 0); + if (rc) + pr_err("Failed to reset CC_SOC_REG rc=%d\n", + rc); + else if (fg_debug_mask & FG_STATUS) + pr_info("Reset SW_CC_SOC to %x\n", batt_soc); + } + + /* + * Schedule the update_temp_work whenever there is a status + * change. This is essential for applying the slope limiter + * coefficients when that feature is enabled. + */ + if (chip->last_temp_update_time && chip->soc_slope_limiter_en) { + cancel_delayed_work_sync(&chip->update_temp_work); + schedule_delayed_work(&chip->update_temp_work, + msecs_to_jiffies(0)); + } + + if (chip->dischg_gain.enable) { + fg_stay_awake(&chip->dischg_gain_wakeup_source); + schedule_work(&chip->dischg_gain_work); + } + get_current_time(¤t_time); /* * When charging status changes, update SRAM parameters if it @@ -3393,10 +4135,10 @@ static void status_change_work(struct work_struct *work) } if ((chip->wa_flag & USE_CC_SOC_REG) && chip->bad_batt_detection_en && chip->safety_timer_expired) { - chip->sw_cc_soc_data.delta_soc = - DIV_ROUND_CLOSEST(abs(cc_soc - - chip->sw_cc_soc_data.init_cc_soc) - * 100, FULL_PERCENT_28BIT); + uint64_t delta_cc_soc = abs(cc_soc - + chip->sw_cc_soc_data.init_cc_soc); + chip->sw_cc_soc_data.delta_soc = DIV_ROUND_CLOSEST_ULL( + delta_cc_soc * 100, FULL_PERCENT_28BIT); chip->sw_cc_soc_data.full_capacity = chip->sw_cc_soc_data.delta_soc + chip->sw_cc_soc_data.init_sys_soc; @@ -3539,6 +4281,395 @@ static int fg_init_batt_temp_state(struct fg_chip *chip) return rc; } +static int fg_restore_cc_soc(struct fg_chip *chip) +{ + int rc; + + if (!chip->use_last_cc_soc || !chip->last_cc_soc) + return 0; + + if (fg_debug_mask & FG_STATUS) + pr_info("Restoring cc_soc: %lld\n", chip->last_cc_soc); + + rc = fg_mem_write(chip, (u8 *)&chip->last_cc_soc, + fg_data[FG_DATA_CC_CHARGE].address, 4, + fg_data[FG_DATA_CC_CHARGE].offset, 0); + if (rc) + pr_err("failed to update CC_SOC rc=%d\n", rc); + else + chip->use_last_cc_soc = false; + + return rc; +} + +#define SRAM_MONOTONIC_SOC_REG 0x574 +#define SRAM_MONOTONIC_SOC_OFFSET 2 +static int fg_restore_soc(struct fg_chip *chip) +{ + int rc; + u16 msoc; + + if (chip->use_last_soc && chip->last_soc) + msoc = DIV_ROUND_CLOSEST(chip->last_soc * 0xFFFF, + FULL_SOC_RAW); + else + return 0; + + if (fg_debug_mask & FG_STATUS) + pr_info("Restored soc: %d\n", msoc); + + rc = fg_mem_write(chip, (u8 *)&msoc, SRAM_MONOTONIC_SOC_REG, 2, + SRAM_MONOTONIC_SOC_OFFSET, 0); + if (rc) + pr_err("failed to write M_SOC_REG rc=%d\n", rc); + + return rc; +} + +#define NOM_CAP_REG 0x4F4 +#define CAPACITY_DELTA_DECIPCT 500 +static int load_battery_aging_data(struct fg_chip *chip) +{ + int rc = 0; + u8 buffer[2]; + int16_t cc_mah; + int64_t delta_cc_uah, pct_nom_cap_uah; + + rc = fg_mem_read(chip, buffer, NOM_CAP_REG, 2, 0, 0); + if (rc) { + pr_err("Failed to read nominal capacitance: %d\n", rc); + goto out; + } + + chip->nom_cap_uah = bcap_uah_2b(buffer); + chip->actual_cap_uah = chip->nom_cap_uah; + + if (chip->learning_data.learned_cc_uah == 0) { + chip->learning_data.learned_cc_uah = chip->nom_cap_uah; + fg_cap_learning_save_data(chip); + } else if (chip->learning_data.feedback_on) { + delta_cc_uah = abs(chip->learning_data.learned_cc_uah - + chip->nom_cap_uah); + pct_nom_cap_uah = div64_s64((int64_t)chip->nom_cap_uah * + CAPACITY_DELTA_DECIPCT, 1000); + /* + * If the learned capacity is out of range, say by 50% + * from the nominal capacity, then overwrite the learned + * capacity with the nominal capacity. + */ + if (chip->nom_cap_uah && delta_cc_uah > pct_nom_cap_uah) { + if (fg_debug_mask & FG_AGING) { + pr_info("learned_cc_uah: %lld is higher than expected\n", + chip->learning_data.learned_cc_uah); + pr_info("Capping it to nominal:%d\n", + chip->nom_cap_uah); + } + chip->learning_data.learned_cc_uah = chip->nom_cap_uah; + fg_cap_learning_save_data(chip); + } else { + cc_mah = div64_s64(chip->learning_data.learned_cc_uah, + 1000); + rc = fg_calc_and_store_cc_soc_coeff(chip, cc_mah); + if (rc) + pr_err("Error in restoring cc_soc_coeff, rc:%d\n", + rc); + } + } +out: + return rc; +} + +static void fg_restore_battery_info(struct fg_chip *chip) +{ + int rc; + char buf[4] = {0, 0, 0, 0}; + + chip->last_soc = DIV_ROUND_CLOSEST(chip->batt_info[BATT_INFO_SOC] * + FULL_SOC_RAW, FULL_CAPACITY); + chip->last_cc_soc = div64_s64((int64_t)chip->last_soc * + FULL_PERCENT_28BIT, FULL_SOC_RAW); + chip->use_last_soc = true; + chip->use_last_cc_soc = true; + rc = fg_restore_soc(chip); + if (rc) { + pr_err("Error in restoring soc, rc=%d\n", rc); + goto out; + } + + rc = fg_restore_cc_soc(chip); + if (rc) { + pr_err("Error in restoring cc_soc, rc=%d\n", rc); + goto out; + } + + rc = fg_mem_write(chip, buf, + fg_data[FG_DATA_VINT_ERR].address, + fg_data[FG_DATA_VINT_ERR].len, + fg_data[FG_DATA_VINT_ERR].offset, 0); + if (rc) { + pr_err("Failed to write to VINT_ERR, rc=%d\n", rc); + goto out; + } + + chip->learning_data.learned_cc_uah = chip->batt_info[BATT_INFO_FCC]; + rc = load_battery_aging_data(chip); + if (rc) { + pr_err("Failed to load battery aging data, rc:%d\n", rc); + goto out; + } + + if (chip->power_supply_registered) + power_supply_changed(chip->bms_psy); + + if (fg_debug_mask & FG_STATUS) + pr_info("Restored battery info!\n"); + +out: + return; +} + +#define DELTA_BATT_TEMP 30 +static bool fg_validate_battery_info(struct fg_chip *chip) +{ + int i, delta_pct, batt_id_kohm, batt_temp, batt_volt_mv, batt_soc; + + for (i = 1; i < BATT_INFO_MAX; i++) { + if (fg_debug_mask & FG_STATUS) + pr_info("batt_info[%d]: %d\n", i, chip->batt_info[i]); + + if ((chip->batt_info[i] == 0 && i != BATT_INFO_TEMP) || + chip->batt_info[i] == INT_MAX) { + if (fg_debug_mask & FG_STATUS) + pr_info("batt_info[%d]:%d is invalid\n", i, + chip->batt_info[i]); + return false; + } + } + + batt_id_kohm = get_sram_prop_now(chip, FG_DATA_BATT_ID) / 1000; + if (batt_id_kohm != chip->batt_info[BATT_INFO_RES_ID]) { + if (fg_debug_mask & FG_STATUS) + pr_info("batt_id(%dK) does not match the stored batt_id(%dK)\n", + batt_id_kohm, + chip->batt_info[BATT_INFO_RES_ID]); + return false; + } + + batt_temp = get_sram_prop_now(chip, FG_DATA_BATT_TEMP); + if (abs(chip->batt_info[BATT_INFO_TEMP] - batt_temp) > + DELTA_BATT_TEMP) { + if (fg_debug_mask & FG_STATUS) + pr_info("batt_temp(%d) is higher/lower than stored batt_temp(%d)\n", + batt_temp, chip->batt_info[BATT_INFO_TEMP]); + return false; + } + + if (chip->batt_info[BATT_INFO_FCC] < 0) { + if (fg_debug_mask & FG_STATUS) + pr_info("batt_fcc cannot be %d\n", + chip->batt_info[BATT_INFO_FCC]); + return false; + } + + batt_volt_mv = get_sram_prop_now(chip, FG_DATA_VOLTAGE) / 1000; + batt_soc = get_monotonic_soc_raw(chip); + if (batt_soc != 0 && batt_soc != FULL_SOC_RAW) + batt_soc = DIV_ROUND_CLOSEST((batt_soc - 1) * + (FULL_CAPACITY - 2), FULL_SOC_RAW - 2) + 1; + + if (*chip->batt_range_ocv && chip->batt_max_voltage_uv > 1000) + delta_pct = DIV_ROUND_CLOSEST(abs(batt_volt_mv - + chip->batt_info[BATT_INFO_VOLTAGE]) * 100, + chip->batt_max_voltage_uv / 1000); + else + delta_pct = abs(batt_soc - chip->batt_info[BATT_INFO_SOC]); + + if (fg_debug_mask & FG_STATUS) + pr_info("Validating by %s batt_voltage:%d capacity:%d delta_pct:%d\n", + *chip->batt_range_ocv ? "OCV" : "SOC", batt_volt_mv, + batt_soc, delta_pct); + + if (*chip->batt_range_pct && delta_pct > *chip->batt_range_pct) { + if (fg_debug_mask & FG_STATUS) + pr_info("delta_pct(%d) is higher than batt_range_pct(%d)\n", + delta_pct, *chip->batt_range_pct); + return false; + } + + return true; +} + +static int fg_set_battery_info(struct fg_chip *chip, int val) +{ + if (chip->batt_info_id < 0 || + chip->batt_info_id >= BATT_INFO_MAX) { + pr_err("Invalid batt_info_id %d\n", chip->batt_info_id); + chip->batt_info_id = 0; + return -EINVAL; + } + + if (chip->batt_info_id == BATT_INFO_NOTIFY && val == INT_MAX - 1) { + if (fg_debug_mask & FG_STATUS) + pr_info("Notified from userspace\n"); + if (chip->batt_info_restore && !chip->ima_error_handling) { + if (!fg_validate_battery_info(chip)) { + if (fg_debug_mask & FG_STATUS) + pr_info("Validating battery info failed\n"); + } else { + fg_restore_battery_info(chip); + } + } + } + + chip->batt_info[chip->batt_info_id] = val; + return 0; +} + +static enum power_supply_property fg_power_props[] = { + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_RAW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_OCV, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_NOW_RAW, + POWER_SUPPLY_PROP_CHARGE_NOW_ERROR, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_COOL_TEMP, + POWER_SUPPLY_PROP_WARM_TEMP, + POWER_SUPPLY_PROP_RESISTANCE, + POWER_SUPPLY_PROP_RESISTANCE_ID, + POWER_SUPPLY_PROP_BATTERY_TYPE, + POWER_SUPPLY_PROP_UPDATE_NOW, + POWER_SUPPLY_PROP_ESR_COUNT, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_CYCLE_COUNT_ID, + POWER_SUPPLY_PROP_HI_POWER, + POWER_SUPPLY_PROP_SOC_REPORTING_READY, + POWER_SUPPLY_PROP_IGNORE_FALSE_NEGATIVE_ISENSE, + POWER_SUPPLY_PROP_ENABLE_JEITA_DETECTION, + POWER_SUPPLY_PROP_BATTERY_INFO, + POWER_SUPPLY_PROP_BATTERY_INFO_ID, +}; + +static int fg_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct fg_chip *chip = power_supply_get_drvdata(psy); + bool vbatt_low_sts; + + switch (psp) { + case POWER_SUPPLY_PROP_BATTERY_TYPE: + if (chip->battery_missing) + val->strval = missing_batt_type; + else if (chip->fg_restarting) + val->strval = loading_batt_type; + else + val->strval = chip->batt_type; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = get_prop_capacity(chip); + break; + case POWER_SUPPLY_PROP_CAPACITY_RAW: + val->intval = get_sram_prop_now(chip, FG_DATA_BATT_SOC); + break; + case POWER_SUPPLY_PROP_CHARGE_NOW_ERROR: + val->intval = get_sram_prop_now(chip, FG_DATA_VINT_ERR); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = get_sram_prop_now(chip, FG_DATA_CURRENT); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = get_sram_prop_now(chip, FG_DATA_VOLTAGE); + break; + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + val->intval = get_sram_prop_now(chip, FG_DATA_OCV); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = chip->batt_max_voltage_uv; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = get_sram_prop_now(chip, FG_DATA_BATT_TEMP); + break; + case POWER_SUPPLY_PROP_COOL_TEMP: + val->intval = get_prop_jeita_temp(chip, FG_MEM_SOFT_COLD); + break; + case POWER_SUPPLY_PROP_WARM_TEMP: + val->intval = get_prop_jeita_temp(chip, FG_MEM_SOFT_HOT); + break; + case POWER_SUPPLY_PROP_RESISTANCE: + val->intval = get_sram_prop_now(chip, FG_DATA_BATT_ESR); + break; + case POWER_SUPPLY_PROP_ESR_COUNT: + val->intval = get_sram_prop_now(chip, FG_DATA_BATT_ESR_COUNT); + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + val->intval = fg_get_cycle_count(chip); + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT_ID: + val->intval = chip->cyc_ctr.id; + break; + case POWER_SUPPLY_PROP_RESISTANCE_ID: + val->intval = get_sram_prop_now(chip, FG_DATA_BATT_ID); + break; + case POWER_SUPPLY_PROP_UPDATE_NOW: + val->intval = 0; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + if (!fg_get_vbatt_status(chip, &vbatt_low_sts)) + val->intval = (int)vbatt_low_sts; + else + val->intval = 1; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = chip->nom_cap_uah; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = chip->learning_data.learned_cc_uah; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = chip->learning_data.cc_uah; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW_RAW: + val->intval = get_sram_prop_now(chip, FG_DATA_CC_CHARGE); + break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + val->intval = fg_get_current_cc(chip); + break; + case POWER_SUPPLY_PROP_HI_POWER: + val->intval = !!chip->bcl_lpm_disabled; + break; + case POWER_SUPPLY_PROP_SOC_REPORTING_READY: + val->intval = !!chip->soc_reporting_ready; + break; + case POWER_SUPPLY_PROP_IGNORE_FALSE_NEGATIVE_ISENSE: + val->intval = !chip->allow_false_negative_isense; + break; + case POWER_SUPPLY_PROP_ENABLE_JEITA_DETECTION: + val->intval = chip->use_soft_jeita_irq; + break; + case POWER_SUPPLY_PROP_BATTERY_INFO: + if (chip->batt_info_id < 0 || + chip->batt_info_id >= BATT_INFO_MAX) + return -EINVAL; + val->intval = chip->batt_info[chip->batt_info_id]; + break; + case POWER_SUPPLY_PROP_BATTERY_INFO_ID: + val->intval = chip->batt_info_id; + break; + default: + return -EINVAL; + } + + return 0; +} + static int fg_power_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) @@ -3557,6 +4688,67 @@ static int fg_power_set_property(struct power_supply *psy, if (val->intval) update_sram_data(chip, &unused); break; + case POWER_SUPPLY_PROP_IGNORE_FALSE_NEGATIVE_ISENSE: + rc = set_prop_ignore_false_negative_isense(chip, !!val->intval); + if (rc) + pr_err("set_prop_ignore_false_negative_isense failed, rc=%d\n", + rc); + else + chip->allow_false_negative_isense = !val->intval; + break; + case POWER_SUPPLY_PROP_ENABLE_JEITA_DETECTION: + if (chip->use_soft_jeita_irq == !!val->intval) { + pr_debug("JEITA irq %s, ignore!\n", + chip->use_soft_jeita_irq ? + "enabled" : "disabled"); + break; + } + chip->use_soft_jeita_irq = !!val->intval; + if (chip->use_soft_jeita_irq) { + if (chip->batt_irq[JEITA_SOFT_COLD].disabled) { + enable_irq( + chip->batt_irq[JEITA_SOFT_COLD].irq); + chip->batt_irq[JEITA_SOFT_COLD].disabled = + false; + } + if (!chip->batt_irq[JEITA_SOFT_COLD].wakeup) { + enable_irq_wake( + chip->batt_irq[JEITA_SOFT_COLD].irq); + chip->batt_irq[JEITA_SOFT_COLD].wakeup = true; + } + if (chip->batt_irq[JEITA_SOFT_HOT].disabled) { + enable_irq( + chip->batt_irq[JEITA_SOFT_HOT].irq); + chip->batt_irq[JEITA_SOFT_HOT].disabled = false; + } + if (!chip->batt_irq[JEITA_SOFT_HOT].wakeup) { + enable_irq_wake( + chip->batt_irq[JEITA_SOFT_HOT].irq); + chip->batt_irq[JEITA_SOFT_HOT].wakeup = true; + } + } else { + if (chip->batt_irq[JEITA_SOFT_COLD].wakeup) { + disable_irq_wake( + chip->batt_irq[JEITA_SOFT_COLD].irq); + chip->batt_irq[JEITA_SOFT_COLD].wakeup = false; + } + if (!chip->batt_irq[JEITA_SOFT_COLD].disabled) { + disable_irq_nosync( + chip->batt_irq[JEITA_SOFT_COLD].irq); + chip->batt_irq[JEITA_SOFT_COLD].disabled = true; + } + if (chip->batt_irq[JEITA_SOFT_HOT].wakeup) { + disable_irq_wake( + chip->batt_irq[JEITA_SOFT_HOT].irq); + chip->batt_irq[JEITA_SOFT_HOT].wakeup = false; + } + if (!chip->batt_irq[JEITA_SOFT_HOT].disabled) { + disable_irq_nosync( + chip->batt_irq[JEITA_SOFT_HOT].irq); + chip->batt_irq[JEITA_SOFT_HOT].disabled = true; + } + } + break; case POWER_SUPPLY_PROP_STATUS: chip->prev_status = chip->status; chip->status = val->intval; @@ -3599,6 +4791,12 @@ static int fg_power_set_property(struct power_supply *psy, schedule_work(&chip->bcl_hi_power_work); } break; + case POWER_SUPPLY_PROP_BATTERY_INFO: + rc = fg_set_battery_info(chip, val->intval); + break; + case POWER_SUPPLY_PROP_BATTERY_INFO_ID: + chip->batt_info_id = val->intval; + break; default: return -EINVAL; }; @@ -3613,6 +4811,8 @@ static int fg_property_is_writeable(struct power_supply *psy, case POWER_SUPPLY_PROP_COOL_TEMP: case POWER_SUPPLY_PROP_WARM_TEMP: case POWER_SUPPLY_PROP_CYCLE_COUNT_ID: + case POWER_SUPPLY_PROP_BATTERY_INFO: + case POWER_SUPPLY_PROP_BATTERY_INFO_ID: return 1; default: break; @@ -3807,21 +5007,197 @@ done: fg_relax(&chip->gain_comp_wakeup_source); } -#define BATT_MISSING_STS BIT(6) -static bool is_battery_missing(struct fg_chip *chip) +static void cc_soc_store_work(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, struct fg_chip, + cc_soc_store_work); + int cc_soc_pct; + + if (!chip->nom_cap_uah) { + pr_err("nom_cap_uah zero!\n"); + fg_relax(&chip->cc_soc_wakeup_source); + return; + } + + cc_soc_pct = get_sram_prop_now(chip, FG_DATA_CC_CHARGE); + cc_soc_pct = div64_s64(cc_soc_pct * 100, + chip->nom_cap_uah); + chip->last_cc_soc = div64_s64((int64_t)chip->last_soc * + FULL_PERCENT_28BIT, FULL_SOC_RAW); + + if (fg_debug_mask & FG_STATUS) + pr_info("cc_soc_pct: %d last_cc_soc: %lld\n", cc_soc_pct, + chip->last_cc_soc); + + if (fg_reset_on_lockup && (chip->cc_soc_limit_pct > 0 && + cc_soc_pct >= chip->cc_soc_limit_pct)) { + pr_err("CC_SOC out of range\n"); + fg_check_ima_error_handling(chip); + } + + fg_relax(&chip->cc_soc_wakeup_source); +} + +#define HARD_JEITA_ALARM_CHECK_NS 10000000000ULL +static enum alarmtimer_restart fg_hard_jeita_alarm_cb(struct alarm *alarm, + ktime_t now) +{ + struct fg_chip *chip = container_of(alarm, + struct fg_chip, hard_jeita_alarm); + int rc, health = POWER_SUPPLY_HEALTH_UNKNOWN; + u8 regval; + bool batt_hot, batt_cold; + union power_supply_propval val = {0, }; + + if (!is_usb_present(chip)) { + pr_debug("USB plugged out, stop the timer!\n"); + return ALARMTIMER_NORESTART; + } + + rc = fg_read(chip, ®val, BATT_INFO_STS(chip->batt_base), 1); + if (rc) { + pr_err("read batt_sts failed, rc=%d\n", rc); + goto recheck; + } + + batt_hot = !!(regval & JEITA_HARD_HOT_RT_STS); + batt_cold = !!(regval & JEITA_HARD_COLD_RT_STS); + if (batt_hot && batt_cold) { + pr_debug("Hot && cold can't co-exist\n"); + goto recheck; + } + + if ((batt_hot == chip->batt_hot) && (batt_cold == chip->batt_cold)) { + pr_debug("battery JEITA state not changed, ignore\n"); + goto recheck; + } + + if (batt_cold != chip->batt_cold) { + /* cool --> cold */ + if (chip->batt_cool) { + chip->batt_cool = false; + chip->batt_cold = true; + health = POWER_SUPPLY_HEALTH_COLD; + } else if (chip->batt_cold) { /* cold --> cool */ + chip->batt_cool = true; + chip->batt_cold = false; + health = POWER_SUPPLY_HEALTH_COOL; + } + } + + if (batt_hot != chip->batt_hot) { + /* warm --> hot */ + if (chip->batt_warm) { + chip->batt_warm = false; + chip->batt_hot = true; + health = POWER_SUPPLY_HEALTH_OVERHEAT; + } else if (chip->batt_hot) { /* hot --> warm */ + chip->batt_hot = false; + chip->batt_warm = true; + health = POWER_SUPPLY_HEALTH_WARM; + } + } + + if (health != POWER_SUPPLY_HEALTH_UNKNOWN) { + pr_debug("FG report battery health: %d\n", health); + val.intval = health; + rc = power_supply_set_property(chip->batt_psy, + POWER_SUPPLY_PROP_HEALTH, &val); + if (rc) + pr_err("Set batt_psy health: %d failed\n", health); + } + +recheck: + alarm_forward_now(alarm, ns_to_ktime(HARD_JEITA_ALARM_CHECK_NS)); + return ALARMTIMER_RESTART; +} + +#define BATT_SOFT_COLD_STS BIT(0) +#define BATT_SOFT_HOT_STS BIT(1) +static irqreturn_t fg_jeita_soft_hot_irq_handler(int irq, void *_chip) { int rc; - u8 fg_batt_sts; + struct fg_chip *chip = _chip; + u8 regval; + bool batt_warm; + union power_supply_propval val = {0, }; - rc = fg_read(chip, &fg_batt_sts, - INT_RT_STS(chip->batt_base), 1); + if (!is_charger_available(chip)) + return IRQ_HANDLED; + + rc = fg_read(chip, ®val, INT_RT_STS(chip->batt_base), 1); if (rc) { pr_err("spmi read failed: addr=%03X, rc=%d\n", INT_RT_STS(chip->batt_base), rc); - return false; + return IRQ_HANDLED; } - return (fg_batt_sts & BATT_MISSING_STS) ? true : false; + batt_warm = !!(regval & BATT_SOFT_HOT_STS); + if (chip->batt_warm == batt_warm) { + pr_debug("warm state not change, ignore!\n"); + return IRQ_HANDLED; + } + + chip->batt_warm = batt_warm; + if (batt_warm) { + val.intval = POWER_SUPPLY_HEALTH_WARM; + power_supply_set_property(chip->batt_psy, + POWER_SUPPLY_PROP_HEALTH, &val); + /* kick the alarm timer for hard hot polling */ + alarm_start_relative(&chip->hard_jeita_alarm, + ns_to_ktime(HARD_JEITA_ALARM_CHECK_NS)); + } else { + val.intval = POWER_SUPPLY_HEALTH_GOOD; + power_supply_set_property(chip->batt_psy, + POWER_SUPPLY_PROP_HEALTH, &val); + /* cancel the alarm timer */ + alarm_try_to_cancel(&chip->hard_jeita_alarm); + } + + return IRQ_HANDLED; +} + +static irqreturn_t fg_jeita_soft_cold_irq_handler(int irq, void *_chip) +{ + int rc; + struct fg_chip *chip = _chip; + u8 regval; + bool batt_cool; + union power_supply_propval val = {0, }; + + if (!is_charger_available(chip)) + return IRQ_HANDLED; + + rc = fg_read(chip, ®val, INT_RT_STS(chip->batt_base), 1); + if (rc) { + pr_err("spmi read failed: addr=%03X, rc=%d\n", + INT_RT_STS(chip->batt_base), rc); + return IRQ_HANDLED; + } + + batt_cool = !!(regval & BATT_SOFT_COLD_STS); + if (chip->batt_cool == batt_cool) { + pr_debug("cool state not change, ignore\n"); + return IRQ_HANDLED; + } + + chip->batt_cool = batt_cool; + if (batt_cool) { + val.intval = POWER_SUPPLY_HEALTH_COOL; + power_supply_set_property(chip->batt_psy, + POWER_SUPPLY_PROP_HEALTH, &val); + /* kick the alarm timer for hard cold polling */ + alarm_start_relative(&chip->hard_jeita_alarm, + ns_to_ktime(HARD_JEITA_ALARM_CHECK_NS)); + } else { + val.intval = POWER_SUPPLY_HEALTH_GOOD; + power_supply_set_property(chip->batt_psy, + POWER_SUPPLY_PROP_HEALTH, &val); + /* cancel the alarm timer */ + alarm_try_to_cancel(&chip->hard_jeita_alarm); + } + + return IRQ_HANDLED; } #define SOC_FIRST_EST_DONE BIT(5) @@ -3841,21 +5217,40 @@ static bool is_first_est_done(struct fg_chip *chip) return (fg_soc_sts & SOC_FIRST_EST_DONE) ? true : false; } +#define FG_EMPTY_DEBOUNCE_MS 1500 static irqreturn_t fg_vbatt_low_handler(int irq, void *_chip) { struct fg_chip *chip = _chip; - int rc; bool vbatt_low_sts; if (fg_debug_mask & FG_IRQS) pr_info("vbatt-low triggered\n"); - if (chip->status == POWER_SUPPLY_STATUS_CHARGING) { - rc = fg_get_vbatt_status(chip, &vbatt_low_sts); - if (rc) { - pr_err("error in reading vbatt_status, rc:%d\n", rc); + /* handle empty soc based on vbatt-low interrupt */ + if (chip->use_vbat_low_empty_soc) { + if (fg_get_vbatt_status(chip, &vbatt_low_sts)) goto out; + + if (vbatt_low_sts) { + if (fg_debug_mask & FG_IRQS) + pr_info("Vbatt is low\n"); + disable_irq_wake(chip->batt_irq[VBATT_LOW].irq); + disable_irq_nosync(chip->batt_irq[VBATT_LOW].irq); + chip->vbat_low_irq_enabled = false; + fg_stay_awake(&chip->empty_check_wakeup_source); + schedule_delayed_work(&chip->check_empty_work, + msecs_to_jiffies(FG_EMPTY_DEBOUNCE_MS)); + } else { + if (fg_debug_mask & FG_IRQS) + pr_info("Vbatt is high\n"); + chip->soc_empty = false; } + goto out; + } + + if (chip->status == POWER_SUPPLY_STATUS_CHARGING) { + if (fg_get_vbatt_status(chip, &vbatt_low_sts)) + goto out; if (!vbatt_low_sts && chip->vbat_low_irq_enabled) { if (fg_debug_mask & FG_IRQS) pr_info("disabling vbatt_low irq\n"); @@ -3876,8 +5271,10 @@ static irqreturn_t fg_batt_missing_irq_handler(int irq, void *_chip) bool batt_missing = is_battery_missing(chip); if (batt_missing) { + fg_cap_learning_stop(chip); chip->battery_missing = true; chip->profile_loaded = false; + chip->soc_reporting_ready = false; chip->batt_type = default_batt_type; mutex_lock(&chip->cyc_ctr.lock); if (fg_debug_mask & FG_IRQS) @@ -3885,17 +5282,10 @@ static irqreturn_t fg_batt_missing_irq_handler(int irq, void *_chip) clear_cycle_counter(chip); mutex_unlock(&chip->cyc_ctr.lock); } else { - if (!chip->use_otp_profile) { - reinit_completion(&chip->batt_id_avail); - reinit_completion(&chip->first_soc_done); - schedule_delayed_work(&chip->batt_profile_init, 0); - cancel_delayed_work(&chip->update_sram_data); - schedule_delayed_work( - &chip->update_sram_data, - msecs_to_jiffies(0)); - } else { + if (!chip->use_otp_profile) + fg_handle_battery_insertion(chip); + else chip->battery_missing = false; - } } if (fg_debug_mask & FG_IRQS) @@ -3943,7 +5333,7 @@ static irqreturn_t fg_soc_irq_handler(int irq, void *_chip) { struct fg_chip *chip = _chip; u8 soc_rt_sts; - int rc; + int rc, msoc; rc = fg_read(chip, &soc_rt_sts, INT_RT_STS(chip->soc_base), 1); if (rc) { @@ -3954,6 +5344,37 @@ static irqreturn_t fg_soc_irq_handler(int irq, void *_chip) if (fg_debug_mask & FG_IRQS) pr_info("triggered 0x%x\n", soc_rt_sts); + if (chip->dischg_gain.enable) { + fg_stay_awake(&chip->dischg_gain_wakeup_source); + schedule_work(&chip->dischg_gain_work); + } + + if (chip->soc_slope_limiter_en) { + fg_stay_awake(&chip->slope_limit_wakeup_source); + schedule_work(&chip->slope_limiter_work); + } + + /* Backup last soc every delta soc interrupt */ + chip->use_last_soc = false; + if (fg_reset_on_lockup) { + if (!chip->ima_error_handling) + chip->last_soc = get_monotonic_soc_raw(chip); + if (fg_debug_mask & FG_STATUS) + pr_info("last_soc: %d\n", chip->last_soc); + + fg_stay_awake(&chip->cc_soc_wakeup_source); + schedule_work(&chip->cc_soc_store_work); + } + + if (chip->use_vbat_low_empty_soc) { + msoc = get_monotonic_soc_raw(chip); + if (msoc == 0 || chip->soc_empty) { + fg_stay_awake(&chip->empty_check_wakeup_source); + schedule_delayed_work(&chip->check_empty_work, + msecs_to_jiffies(FG_EMPTY_DEBOUNCE_MS)); + } + } + schedule_work(&chip->battery_age_work); if (chip->power_supply_registered) @@ -3988,7 +5409,6 @@ static irqreturn_t fg_soc_irq_handler(int irq, void *_chip) return IRQ_HANDLED; } -#define FG_EMPTY_DEBOUNCE_MS 1500 static irqreturn_t fg_empty_soc_irq_handler(int irq, void *_chip) { struct fg_chip *chip = _chip; @@ -4100,16 +5520,15 @@ done: fg_relax(&chip->resume_soc_wakeup_source); } - #define OCV_COEFFS_START_REG 0x4C0 #define OCV_JUNCTION_REG 0x4D8 -#define NOM_CAP_REG 0x4F4 #define CUTOFF_VOLTAGE_REG 0x40C #define RSLOW_CFG_REG 0x538 #define RSLOW_CFG_OFFSET 2 #define RSLOW_THRESH_REG 0x52C #define RSLOW_THRESH_OFFSET 0 -#define TEMP_RS_TO_RSLOW_OFFSET 2 +#define RS_TO_RSLOW_CHG_OFFSET 2 +#define RS_TO_RSLOW_DISCHG_OFFSET 0 #define RSLOW_COMP_REG 0x528 #define RSLOW_COMP_C1_OFFSET 0 #define RSLOW_COMP_C2_OFFSET 2 @@ -4117,7 +5536,6 @@ static int populate_system_data(struct fg_chip *chip) { u8 buffer[24]; int rc, i; - int16_t cc_mah; fg_mem_lock(chip); rc = fg_mem_read(chip, buffer, OCV_COEFFS_START_REG, 24, 0, 0); @@ -4138,30 +5556,21 @@ static int populate_system_data(struct fg_chip *chip) chip->ocv_coeffs[8], chip->ocv_coeffs[9], chip->ocv_coeffs[10], chip->ocv_coeffs[11]); } - rc = fg_mem_read(chip, buffer, OCV_JUNCTION_REG, 1, 0, 0); - chip->ocv_junction_p1p2 = buffer[0] * 100 / 255; - rc |= fg_mem_read(chip, buffer, OCV_JUNCTION_REG, 1, 1, 0); - chip->ocv_junction_p2p3 = buffer[0] * 100 / 255; + rc = fg_mem_read(chip, buffer, OCV_JUNCTION_REG, 2, 0, 0); if (rc) { pr_err("Failed to read ocv junctions: %d\n", rc); goto done; } - rc = fg_mem_read(chip, buffer, NOM_CAP_REG, 2, 0, 0); + + chip->ocv_junction_p1p2 = buffer[0] * 100 / 255; + chip->ocv_junction_p2p3 = buffer[1] * 100 / 255; + + rc = load_battery_aging_data(chip); if (rc) { - pr_err("Failed to read nominal capacitance: %d\n", rc); + pr_err("Failed to load battery aging data, rc:%d\n", rc); goto done; } - chip->nom_cap_uah = bcap_uah_2b(buffer); - chip->actual_cap_uah = chip->nom_cap_uah; - if (chip->learning_data.learned_cc_uah == 0) { - chip->learning_data.learned_cc_uah = chip->nom_cap_uah; - fg_cap_learning_save_data(chip); - } else if (chip->learning_data.feedback_on) { - cc_mah = div64_s64(chip->learning_data.learned_cc_uah, 1000); - rc = fg_calc_and_store_cc_soc_coeff(chip, cc_mah); - if (rc) - pr_err("Error in restoring cc_soc_coeff, rc:%d\n", rc); - } + rc = fg_mem_read(chip, buffer, CUTOFF_VOLTAGE_REG, 2, 0, 0); if (rc) { pr_err("Failed to read cutoff voltage: %d\n", rc); @@ -4188,9 +5597,9 @@ static int populate_system_data(struct fg_chip *chip) } chip->rslow_comp.rslow_thr = buffer[0]; rc = fg_mem_read(chip, buffer, TEMP_RS_TO_RSLOW_REG, 2, - RSLOW_THRESH_OFFSET, 0); + RS_TO_RSLOW_CHG_OFFSET, 0); if (rc) { - pr_err("unable to read rs to rslow: %d\n", rc); + pr_err("unable to read rs to rslow_chg: %d\n", rc); goto done; } memcpy(chip->rslow_comp.rs_to_rslow, buffer, 2); @@ -4207,6 +5616,68 @@ done: return rc; } +static int fg_update_batt_rslow_settings(struct fg_chip *chip) +{ + int64_t rs_to_rslow_chg, rs_to_rslow_dischg, batt_esr, rconn_uohm; + u8 buffer[2]; + int rc; + + rc = fg_mem_read(chip, buffer, BATTERY_ESR_REG, 2, ESR_OFFSET, 0); + if (rc) { + pr_err("unable to read battery_esr: %d\n", rc); + goto done; + } + batt_esr = half_float(buffer); + + rc = fg_mem_read(chip, buffer, TEMP_RS_TO_RSLOW_REG, 2, + RS_TO_RSLOW_DISCHG_OFFSET, 0); + if (rc) { + pr_err("unable to read rs to rslow dischg: %d\n", rc); + goto done; + } + rs_to_rslow_dischg = half_float(buffer); + + rc = fg_mem_read(chip, buffer, TEMP_RS_TO_RSLOW_REG, 2, + RS_TO_RSLOW_CHG_OFFSET, 0); + if (rc) { + pr_err("unable to read rs to rslow chg: %d\n", rc); + goto done; + } + rs_to_rslow_chg = half_float(buffer); + + if (fg_debug_mask & FG_STATUS) + pr_info("rs_rslow_chg: %lld, rs_rslow_dischg: %lld, esr: %lld\n", + rs_to_rslow_chg, rs_to_rslow_dischg, batt_esr); + + rconn_uohm = chip->rconn_mohm * 1000; + rs_to_rslow_dischg = div64_s64(rs_to_rslow_dischg * batt_esr, + batt_esr + rconn_uohm); + rs_to_rslow_chg = div64_s64(rs_to_rslow_chg * batt_esr, + batt_esr + rconn_uohm); + + half_float_to_buffer(rs_to_rslow_chg, buffer); + rc = fg_mem_write(chip, buffer, TEMP_RS_TO_RSLOW_REG, 2, + RS_TO_RSLOW_CHG_OFFSET, 0); + if (rc) { + pr_err("unable to write rs_to_rslow_chg: %d\n", rc); + goto done; + } + + half_float_to_buffer(rs_to_rslow_dischg, buffer); + rc = fg_mem_write(chip, buffer, TEMP_RS_TO_RSLOW_REG, 2, + RS_TO_RSLOW_DISCHG_OFFSET, 0); + if (rc) { + pr_err("unable to write rs_to_rslow_dischg: %d\n", rc); + goto done; + } + + if (fg_debug_mask & FG_STATUS) + pr_info("Modified rs_rslow_chg: %lld, rs_rslow_dischg: %lld\n", + rs_to_rslow_chg, rs_to_rslow_dischg); +done: + return rc; +} + #define RSLOW_CFG_MASK (BIT(2) | BIT(3) | BIT(4) | BIT(5)) #define RSLOW_CFG_ON_VAL (BIT(2) | BIT(3)) #define RSLOW_THRESH_FULL_VAL 0xFF @@ -4233,7 +5704,7 @@ static int fg_rslow_charge_comp_set(struct fg_chip *chip) half_float_to_buffer(chip->rslow_comp.chg_rs_to_rslow, buffer); rc = fg_mem_write(chip, buffer, - TEMP_RS_TO_RSLOW_REG, 2, TEMP_RS_TO_RSLOW_OFFSET, 0); + TEMP_RS_TO_RSLOW_REG, 2, RS_TO_RSLOW_CHG_OFFSET, 0); if (rc) { pr_err("unable to write rs to rslow: %d\n", rc); goto done; @@ -4286,7 +5757,7 @@ static int fg_rslow_charge_comp_clear(struct fg_chip *chip) } rc = fg_mem_write(chip, chip->rslow_comp.rs_to_rslow, - TEMP_RS_TO_RSLOW_REG, 2, TEMP_RS_TO_RSLOW_OFFSET, 0); + TEMP_RS_TO_RSLOW_REG, 2, RS_TO_RSLOW_CHG_OFFSET, 0); if (rc) { pr_err("unable to write rs to rslow: %d\n", rc); goto done; @@ -4510,6 +5981,58 @@ static void esr_extract_config_work(struct work_struct *work) fg_relax(&chip->esr_extract_wakeup_source); } +#define KI_COEFF_MEDC_REG 0x400 +#define KI_COEFF_MEDC_OFFSET 0 +#define KI_COEFF_HIGHC_REG 0x404 +#define KI_COEFF_HIGHC_OFFSET 0 +#define DEFAULT_MEDC_VOLTAGE_GAIN 3 +#define DEFAULT_HIGHC_VOLTAGE_GAIN 2 +static void discharge_gain_work(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, struct fg_chip, + dischg_gain_work); + u8 buf[2]; + int capacity, rc, i; + int64_t medc_val = DEFAULT_MEDC_VOLTAGE_GAIN; + int64_t highc_val = DEFAULT_HIGHC_VOLTAGE_GAIN; + + capacity = get_prop_capacity(chip); + if (chip->status == POWER_SUPPLY_STATUS_DISCHARGING) { + for (i = VOLT_GAIN_MAX - 1; i >= 0; i--) { + if (capacity <= chip->dischg_gain.soc[i]) { + medc_val = chip->dischg_gain.medc_gain[i]; + highc_val = chip->dischg_gain.highc_gain[i]; + } + } + } + + if (fg_debug_mask & FG_STATUS) + pr_info("Capacity: %d, medc_gain: %lld highc_gain: %lld\n", + capacity, medc_val, highc_val); + + medc_val *= MICRO_UNIT; + half_float_to_buffer(medc_val, buf); + rc = fg_mem_write(chip, buf, KI_COEFF_MEDC_REG, 2, + KI_COEFF_MEDC_OFFSET, 0); + if (rc) + pr_err("Couldn't write to ki_coeff_medc_reg, rc=%d\n", rc); + else if (fg_debug_mask & FG_STATUS) + pr_info("Value [%x %x] written to ki_coeff_medc\n", buf[0], + buf[1]); + + highc_val *= MICRO_UNIT; + half_float_to_buffer(highc_val, buf); + rc = fg_mem_write(chip, buf, KI_COEFF_HIGHC_REG, 2, + KI_COEFF_HIGHC_OFFSET, 0); + if (rc) + pr_err("Couldn't write to ki_coeff_highc_reg, rc=%d\n", rc); + else if (fg_debug_mask & FG_STATUS) + pr_info("Value [%x %x] written to ki_coeff_highc\n", buf[0], + buf[1]); + + fg_relax(&chip->dischg_gain_wakeup_source); +} + #define LOW_LATENCY BIT(6) #define BATT_PROFILE_OFFSET 0x4C0 #define PROFILE_INTEGRITY_REG 0x53C @@ -4529,7 +6052,7 @@ static int fg_do_restart(struct fg_chip *chip, bool write_profile) pr_info("restarting fuel gauge...\n"); try_again: - if (write_profile) { + if (write_profile && !chip->ima_error_handling) { if (!chip->charging_disabled) { pr_err("Charging not yet disabled!\n"); return -EINVAL; @@ -4770,7 +6293,8 @@ fail: #define BATTERY_PSY_WAIT_MS 2000 static int fg_batt_profile_init(struct fg_chip *chip) { - int rc = 0, ret, len, batt_id; + int rc = 0, ret; + int len, batt_id; struct device_node *node = chip->pdev->dev.of_node; struct device_node *batt_node, *profile_node; const char *data, *batt_type_str; @@ -4792,6 +6316,19 @@ wait: goto no_profile; } + /* Check whether the charger is ready */ + if (!is_charger_available(chip)) + goto reschedule; + + /* Disable charging for a FG cycle before calculating vbat_in_range */ + if (!chip->charging_disabled) { + rc = set_prop_enable_charging(chip, false); + if (rc) + pr_err("Failed to disable charging, rc=%d\n", rc); + + goto update; + } + batt_node = of_find_node_by_name(node, "qcom,battery-data"); if (!batt_node) { pr_warn("No available batterydata, using OTP defaults\n"); @@ -4808,8 +6345,12 @@ wait: fg_batt_type); if (IS_ERR_OR_NULL(profile_node)) { rc = PTR_ERR(profile_node); - pr_err("couldn't find profile handle %d\n", rc); - goto no_profile; + if (rc == -EPROBE_DEFER) { + goto reschedule; + } else { + pr_err("couldn't find profile handle rc=%d\n", rc); + goto no_profile; + } } /* read rslow compensation values if they're available */ @@ -4903,18 +6444,6 @@ wait: goto no_profile; } - /* Check whether the charger is ready */ - if (!is_charger_available(chip)) - goto reschedule; - - /* Disable charging for a FG cycle before calculating vbat_in_range */ - if (!chip->charging_disabled) { - rc = set_prop_enable_charging(chip, false); - if (rc) - pr_err("Failed to disable charging, rc=%d\n", rc); - - goto reschedule; - } vbat_in_range = get_vbat_est_diff(chip) < settings[FG_MEM_VBAT_EST_DIFF].value * 1000; @@ -4956,11 +6485,7 @@ wait: chip->batt_profile, len, false); } - if (chip->power_supply_registered) - power_supply_changed(chip->bms_psy); - memcpy(chip->batt_profile, data, len); - chip->batt_profile_len = len; if (fg_debug_mask & FG_STATUS) @@ -4995,6 +6520,11 @@ wait: } } + if (chip->rconn_mohm > 0) { + rc = fg_update_batt_rslow_settings(chip); + if (rc) + pr_err("Error in updating ESR, rc=%d\n", rc); + } done: if (chip->charging_disabled) { rc = set_prop_enable_charging(chip, true); @@ -5008,8 +6538,22 @@ done: chip->batt_type = fg_batt_type; else chip->batt_type = batt_type_str; + + if (chip->first_profile_loaded && fg_reset_on_lockup) { + if (fg_debug_mask & FG_STATUS) + pr_info("restoring SRAM registers\n"); + rc = fg_backup_sram_registers(chip, false); + if (rc) + pr_err("Couldn't restore sram registers\n"); + + /* Read the cycle counter back from FG SRAM */ + if (chip->cyc_ctr.en) + restore_cycle_counter(chip); + } + chip->first_profile_loaded = true; chip->profile_loaded = true; + chip->soc_reporting_ready = true; chip->battery_missing = is_battery_missing(chip); update_chg_iterm(chip); update_cc_cv_setpoint(chip); @@ -5025,8 +6569,10 @@ done: fg_relax(&chip->profile_wakeup_source); pr_info("Battery SOC: %d, V: %duV\n", get_prop_capacity(chip), fg_data[FG_DATA_VOLTAGE].value); + complete_all(&chip->fg_reset_done); return rc; no_profile: + chip->soc_reporting_ready = true; if (chip->charging_disabled) { rc = set_prop_enable_charging(chip, true); if (rc) @@ -5039,14 +6585,15 @@ no_profile: power_supply_changed(chip->bms_psy); fg_relax(&chip->profile_wakeup_source); return rc; -reschedule: - schedule_delayed_work( - &chip->batt_profile_init, - msecs_to_jiffies(BATTERY_PSY_WAIT_MS)); +update: cancel_delayed_work(&chip->update_sram_data); schedule_delayed_work( &chip->update_sram_data, msecs_to_jiffies(0)); +reschedule: + schedule_delayed_work( + &chip->batt_profile_init, + msecs_to_jiffies(BATTERY_PSY_WAIT_MS)); fg_relax(&chip->profile_wakeup_source); return 0; } @@ -5056,14 +6603,41 @@ static void check_empty_work(struct work_struct *work) struct fg_chip *chip = container_of(work, struct fg_chip, check_empty_work.work); + bool vbatt_low_sts; + int msoc; + + /* handle empty soc based on vbatt-low interrupt */ + if (chip->use_vbat_low_empty_soc) { + if (fg_get_vbatt_status(chip, &vbatt_low_sts)) + goto out; + + msoc = get_monotonic_soc_raw(chip); - if (fg_is_batt_empty(chip)) { + if (fg_debug_mask & FG_STATUS) + pr_info("Vbatt_low: %d, msoc: %d\n", vbatt_low_sts, + msoc); + if (vbatt_low_sts || (msoc == 0)) + chip->soc_empty = true; + else + chip->soc_empty = false; + + if (chip->power_supply_registered) + power_supply_changed(chip->bms_psy); + + if (!chip->vbat_low_irq_enabled) { + enable_irq(chip->batt_irq[VBATT_LOW].irq); + enable_irq_wake(chip->batt_irq[VBATT_LOW].irq); + chip->vbat_low_irq_enabled = true; + } + } else if (fg_is_batt_empty(chip)) { if (fg_debug_mask & FG_STATUS) pr_info("EMPTY SOC high\n"); chip->soc_empty = true; if (chip->power_supply_registered) power_supply_changed(chip->bms_psy); } + +out: fg_relax(&chip->empty_check_wakeup_source); } @@ -5103,7 +6677,7 @@ static void charge_full_work(struct work_struct *work) int rc; u8 buffer[3]; int bsoc; - int resume_soc_raw = FULL_SOC_RAW - settings[FG_MEM_RESUME_SOC].value; + int resume_soc_raw = settings[FG_MEM_RESUME_SOC].value; bool disable = false; u8 reg; @@ -5318,6 +6892,98 @@ do { \ } \ } while (0) +static int fg_dischg_gain_dt_init(struct fg_chip *chip) +{ + struct device_node *node = chip->pdev->dev.of_node; + struct property *prop; + int i, rc = 0; + size_t size; + + prop = of_find_property(node, "qcom,fg-dischg-voltage-gain-soc", + NULL); + if (!prop) { + pr_err("qcom-fg-dischg-voltage-gain-soc not specified\n"); + goto out; + } + + size = prop->length / sizeof(u32); + if (size != VOLT_GAIN_MAX) { + pr_err("Voltage gain SOC specified is of incorrect size\n"); + goto out; + } + + rc = of_property_read_u32_array(node, + "qcom,fg-dischg-voltage-gain-soc", chip->dischg_gain.soc, size); + if (rc < 0) { + pr_err("Reading qcom-fg-dischg-voltage-gain-soc failed, rc=%d\n", + rc); + goto out; + } + + for (i = 0; i < VOLT_GAIN_MAX; i++) { + if (chip->dischg_gain.soc[i] > 100) { + pr_err("Incorrect dischg-voltage-gain-soc\n"); + goto out; + } + } + + prop = of_find_property(node, "qcom,fg-dischg-med-voltage-gain", + NULL); + if (!prop) { + pr_err("qcom-fg-dischg-med-voltage-gain not specified\n"); + goto out; + } + + size = prop->length / sizeof(u32); + if (size != VOLT_GAIN_MAX) { + pr_err("med-voltage-gain specified is of incorrect size\n"); + goto out; + } + + rc = of_property_read_u32_array(node, + "qcom,fg-dischg-med-voltage-gain", chip->dischg_gain.medc_gain, + size); + if (rc < 0) { + pr_err("Reading qcom-fg-dischg-med-voltage-gain failed, rc=%d\n", + rc); + goto out; + } + + prop = of_find_property(node, "qcom,fg-dischg-high-voltage-gain", + NULL); + if (!prop) { + pr_err("qcom-fg-dischg-high-voltage-gain not specified\n"); + goto out; + } + + size = prop->length / sizeof(u32); + if (size != VOLT_GAIN_MAX) { + pr_err("high-voltage-gain specified is of incorrect size\n"); + goto out; + } + + rc = of_property_read_u32_array(node, + "qcom,fg-dischg-high-voltage-gain", + chip->dischg_gain.highc_gain, size); + if (rc < 0) { + pr_err("Reading qcom-fg-dischg-high-voltage-gain failed, rc=%d\n", + rc); + goto out; + } + + if (fg_debug_mask & FG_STATUS) { + for (i = 0; i < VOLT_GAIN_MAX; i++) + pr_info("SOC:%d MedC_Gain:%d HighC_Gain: %d\n", + chip->dischg_gain.soc[i], + chip->dischg_gain.medc_gain[i], + chip->dischg_gain.highc_gain[i]); + } + return 0; +out: + chip->dischg_gain.enable = false; + return rc; +} + #define DEFAULT_EVALUATION_CURRENT_MA 1000 static int fg_of_init(struct fg_chip *chip) { @@ -5395,6 +7061,10 @@ static int fg_of_init(struct fg_chip *chip) "cl-max-start-capacity", rc, 15); OF_READ_PROPERTY(chip->learning_data.vbat_est_thr_uv, "cl-vbat-est-thr-uv", rc, 40000); + OF_READ_PROPERTY(chip->learning_data.max_cap_limit, + "cl-max-limit-deciperc", rc, 0); + OF_READ_PROPERTY(chip->learning_data.min_cap_limit, + "cl-min-limit-deciperc", rc, 0); OF_READ_PROPERTY(chip->evaluation_current, "aging-eval-current-ma", rc, DEFAULT_EVALUATION_CURRENT_MA); @@ -5455,6 +7125,77 @@ static int fg_of_init(struct fg_chip *chip) chip->esr_pulse_tune_en = of_property_read_bool(node, "qcom,esr-pulse-tuning-en"); + chip->soc_slope_limiter_en = of_property_read_bool(node, + "qcom,fg-control-slope-limiter"); + if (chip->soc_slope_limiter_en) { + OF_READ_PROPERTY(chip->slope_limit_temp, + "fg-slope-limit-temp-threshold", rc, + SLOPE_LIMIT_TEMP_THRESHOLD); + + OF_READ_PROPERTY(chip->slope_limit_coeffs[LOW_TEMP_CHARGE], + "fg-slope-limit-low-temp-chg", rc, + SLOPE_LIMIT_LOW_TEMP_CHG); + + OF_READ_PROPERTY(chip->slope_limit_coeffs[HIGH_TEMP_CHARGE], + "fg-slope-limit-high-temp-chg", rc, + SLOPE_LIMIT_HIGH_TEMP_CHG); + + OF_READ_PROPERTY(chip->slope_limit_coeffs[LOW_TEMP_DISCHARGE], + "fg-slope-limit-low-temp-dischg", rc, + SLOPE_LIMIT_LOW_TEMP_DISCHG); + + OF_READ_PROPERTY(chip->slope_limit_coeffs[HIGH_TEMP_DISCHARGE], + "fg-slope-limit-high-temp-dischg", rc, + SLOPE_LIMIT_HIGH_TEMP_DISCHG); + + if (fg_debug_mask & FG_STATUS) + pr_info("slope-limiter, temp: %d coeffs: [%d %d %d %d]\n", + chip->slope_limit_temp, + chip->slope_limit_coeffs[LOW_TEMP_CHARGE], + chip->slope_limit_coeffs[HIGH_TEMP_CHARGE], + chip->slope_limit_coeffs[LOW_TEMP_DISCHARGE], + chip->slope_limit_coeffs[HIGH_TEMP_DISCHARGE]); + } + + OF_READ_PROPERTY(chip->rconn_mohm, "fg-rconn-mohm", rc, 0); + + chip->dischg_gain.enable = of_property_read_bool(node, + "qcom,fg-dischg-voltage-gain-ctrl"); + if (chip->dischg_gain.enable) { + rc = fg_dischg_gain_dt_init(chip); + if (rc) { + pr_err("Error in reading dischg_gain parameters, rc=%d\n", + rc); + rc = 0; + } + } + + chip->use_vbat_low_empty_soc = of_property_read_bool(node, + "qcom,fg-use-vbat-low-empty-soc"); + + OF_READ_PROPERTY(chip->batt_temp_low_limit, + "fg-batt-temp-low-limit", rc, BATT_TEMP_LOW_LIMIT); + + OF_READ_PROPERTY(chip->batt_temp_high_limit, + "fg-batt-temp-high-limit", rc, BATT_TEMP_HIGH_LIMIT); + + if (fg_debug_mask & FG_STATUS) + pr_info("batt-temp-low_limit: %d batt-temp-high_limit: %d\n", + chip->batt_temp_low_limit, chip->batt_temp_high_limit); + + OF_READ_PROPERTY(chip->cc_soc_limit_pct, "fg-cc-soc-limit-pct", rc, 0); + + if (fg_debug_mask & FG_STATUS) + pr_info("cc-soc-limit-pct: %d\n", chip->cc_soc_limit_pct); + + chip->batt_info_restore = of_property_read_bool(node, + "qcom,fg-restore-batt-info"); + + if (fg_debug_mask & FG_STATUS) + pr_info("restore: %d validate_by_ocv: %d range_pct: %d\n", + chip->batt_info_restore, fg_batt_valid_ocv, + fg_batt_range_pct); + return rc; } @@ -5528,15 +7269,22 @@ static int fg_init_irqs(struct fg_chip *chip) chip->soc_irq[FULL_SOC].irq, rc); return rc; } - rc = devm_request_irq(chip->dev, - chip->soc_irq[EMPTY_SOC].irq, - fg_empty_soc_irq_handler, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - "empty-soc", chip); - if (rc < 0) { - pr_err("Can't request %d empty-soc: %d\n", - chip->soc_irq[EMPTY_SOC].irq, rc); - return rc; + enable_irq_wake(chip->soc_irq[FULL_SOC].irq); + chip->full_soc_irq_enabled = true; + + if (!chip->use_vbat_low_empty_soc) { + rc = devm_request_irq(chip->dev, + chip->soc_irq[EMPTY_SOC].irq, + fg_empty_soc_irq_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + "empty-soc", chip); + if (rc < 0) { + pr_err("Can't request %d empty-soc: %d\n", + chip->soc_irq[EMPTY_SOC].irq, + rc); + return rc; + } } rc = devm_request_irq(chip->dev, chip->soc_irq[DELTA_SOC].irq, @@ -5558,8 +7306,8 @@ static int fg_init_irqs(struct fg_chip *chip) } enable_irq_wake(chip->soc_irq[DELTA_SOC].irq); - enable_irq_wake(chip->soc_irq[FULL_SOC].irq); - enable_irq_wake(chip->soc_irq[EMPTY_SOC].irq); + if (!chip->use_vbat_low_empty_soc) + enable_irq_wake(chip->soc_irq[EMPTY_SOC].irq); break; case FG_MEMIF: chip->mem_irq[FG_MEM_AVAIL].irq @@ -5581,8 +7329,53 @@ static int fg_init_irqs(struct fg_chip *chip) } break; case FG_BATT: - chip->batt_irq[BATT_MISSING].irq - = of_irq_get_byname(child, "batt-missing"); + chip->batt_irq[JEITA_SOFT_COLD].irq = + of_irq_get_byname(child, "soft-cold"); + if (chip->batt_irq[JEITA_SOFT_COLD].irq < 0) { + pr_err("Unable to get soft-cold irq\n"); + rc = -EINVAL; + return rc; + } + rc = devm_request_threaded_irq(chip->dev, + chip->batt_irq[JEITA_SOFT_COLD].irq, + NULL, + fg_jeita_soft_cold_irq_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "soft-cold", chip); + if (rc < 0) { + pr_err("Can't request %d soft-cold: %d\n", + chip->batt_irq[JEITA_SOFT_COLD].irq, + rc); + return rc; + } + disable_irq(chip->batt_irq[JEITA_SOFT_COLD].irq); + chip->batt_irq[JEITA_SOFT_COLD].disabled = true; + chip->batt_irq[JEITA_SOFT_HOT].irq = + of_irq_get_byname(child, "soft-hot"); + if (chip->batt_irq[JEITA_SOFT_HOT].irq < 0) { + pr_err("Unable to get soft-hot irq\n"); + rc = -EINVAL; + return rc; + } + rc = devm_request_threaded_irq(chip->dev, + chip->batt_irq[JEITA_SOFT_HOT].irq, + NULL, + fg_jeita_soft_hot_irq_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "soft-hot", chip); + if (rc < 0) { + pr_err("Can't request %d soft-hot: %d\n", + chip->batt_irq[JEITA_SOFT_HOT].irq, rc); + return rc; + } + disable_irq(chip->batt_irq[JEITA_SOFT_HOT].irq); + chip->batt_irq[JEITA_SOFT_HOT].disabled = true; + chip->batt_irq[BATT_MISSING].irq = + of_irq_get_byname(child, "batt-missing"); if (chip->batt_irq[BATT_MISSING].irq < 0) { pr_err("Unable to get batt-missing irq\n"); rc = -EINVAL; @@ -5619,8 +7412,14 @@ static int fg_init_irqs(struct fg_chip *chip) chip->batt_irq[VBATT_LOW].irq, rc); return rc; } - disable_irq_nosync(chip->batt_irq[VBATT_LOW].irq); - chip->vbat_low_irq_enabled = false; + if (chip->use_vbat_low_empty_soc) { + enable_irq_wake(chip->batt_irq[VBATT_LOW].irq); + chip->vbat_low_irq_enabled = true; + } else { + disable_irq_nosync( + chip->batt_irq[VBATT_LOW].irq); + chip->vbat_low_irq_enabled = false; + } break; case FG_ADC: break; @@ -5630,17 +7429,22 @@ static int fg_init_irqs(struct fg_chip *chip) } } + chip->irqs_enabled = true; return rc; } -static void fg_cleanup(struct fg_chip *chip) +static void fg_cancel_all_works(struct fg_chip *chip) { + cancel_delayed_work_sync(&chip->check_sanity_work); cancel_delayed_work_sync(&chip->update_sram_data); cancel_delayed_work_sync(&chip->update_temp_work); cancel_delayed_work_sync(&chip->update_jeita_setting); cancel_delayed_work_sync(&chip->check_empty_work); cancel_delayed_work_sync(&chip->batt_profile_init); alarm_try_to_cancel(&chip->fg_cap_learning_alarm); + alarm_try_to_cancel(&chip->hard_jeita_alarm); + if (!chip->ima_error_handling) + cancel_work_sync(&chip->ima_error_recovery_work); cancel_work_sync(&chip->rslow_comp_work); cancel_work_sync(&chip->set_resume_soc_work); cancel_work_sync(&chip->fg_cap_learning_work); @@ -5652,12 +7456,23 @@ static void fg_cleanup(struct fg_chip *chip) cancel_work_sync(&chip->gain_comp_work); cancel_work_sync(&chip->init_work); cancel_work_sync(&chip->charge_full_work); + cancel_work_sync(&chip->bcl_hi_power_work); cancel_work_sync(&chip->esr_extract_config_work); + cancel_work_sync(&chip->slope_limiter_work); + cancel_work_sync(&chip->dischg_gain_work); + cancel_work_sync(&chip->cc_soc_store_work); +} + +static void fg_cleanup(struct fg_chip *chip) +{ + fg_cancel_all_works(chip); + power_supply_unregister(chip->bms_psy); mutex_destroy(&chip->rslow_comp.lock); mutex_destroy(&chip->rw_lock); mutex_destroy(&chip->cyc_ctr.lock); mutex_destroy(&chip->learning_data.learning_lock); mutex_destroy(&chip->sysfs_restart_lock); + mutex_destroy(&chip->ima_recovery_lock); wakeup_source_trash(&chip->resume_soc_wakeup_source.source); wakeup_source_trash(&chip->empty_check_wakeup_source.source); wakeup_source_trash(&chip->memif_wakeup_source.source); @@ -5667,6 +7482,11 @@ static void fg_cleanup(struct fg_chip *chip) wakeup_source_trash(&chip->gain_comp_wakeup_source.source); wakeup_source_trash(&chip->capacity_learning_wakeup_source.source); wakeup_source_trash(&chip->esr_extract_wakeup_source.source); + wakeup_source_trash(&chip->slope_limit_wakeup_source.source); + wakeup_source_trash(&chip->dischg_gain_wakeup_source.source); + wakeup_source_trash(&chip->fg_reset_wakeup_source.source); + wakeup_source_trash(&chip->cc_soc_wakeup_source.source); + wakeup_source_trash(&chip->sanity_wakeup_source.source); } static int fg_remove(struct platform_device *pdev) @@ -6155,12 +7975,13 @@ static int bcl_trim_workaround(struct fg_chip *chip) return 0; } -#define FG_ALG_SYSCTL_1 0x4B0 -#define SOC_CNFG 0x450 -#define SOC_DELTA_OFFSET 3 -#define DELTA_SOC_PERCENT 1 -#define I_TERM_QUAL_BIT BIT(1) -#define PATCH_NEG_CURRENT_BIT BIT(3) +#define FG_ALG_SYSCTL_1 0x4B0 +#define SOC_CNFG 0x450 +#define SOC_DELTA_OFFSET 3 +#define DELTA_SOC_PERCENT 1 +#define ALERT_CFG_OFFSET 3 +#define I_TERM_QUAL_BIT BIT(1) +#define PATCH_NEG_CURRENT_BIT BIT(3) #define KI_COEFF_PRED_FULL_ADDR 0x408 #define KI_COEFF_PRED_FULL_4_0_MSB 0x88 #define KI_COEFF_PRED_FULL_4_0_LSB 0x00 @@ -6168,6 +7989,12 @@ static int bcl_trim_workaround(struct fg_chip *chip) #define FG_ADC_CONFIG_REG 0x4B8 #define FG_BCL_CONFIG_OFFSET 0x3 #define BCL_FORCED_HPM_IN_CHARGE BIT(2) +#define IRQ_USE_VOLTAGE_HYST_BIT BIT(0) +#define EMPTY_FROM_VOLTAGE_BIT BIT(1) +#define EMPTY_FROM_SOC_BIT BIT(2) +#define EMPTY_SOC_IRQ_MASK (IRQ_USE_VOLTAGE_HYST_BIT | \ + EMPTY_FROM_SOC_BIT | \ + EMPTY_FROM_VOLTAGE_BIT) static int fg_common_hw_init(struct fg_chip *chip) { int rc; @@ -6176,8 +8003,9 @@ static int fg_common_hw_init(struct fg_chip *chip) update_iterm(chip); update_cutoff_voltage(chip); - update_irq_volt_empty(chip); update_bcl_thresholds(chip); + if (!chip->use_vbat_low_empty_soc) + update_irq_volt_empty(chip); resume_soc_raw = settings[FG_MEM_RESUME_SOC].value; if (resume_soc_raw > 0) { @@ -6207,6 +8035,11 @@ static int fg_common_hw_init(struct fg_chip *chip) return rc; } + /* Override the voltage threshold for vbatt_low with empty_volt */ + if (chip->use_vbat_low_empty_soc) + settings[FG_MEM_BATT_LOW].value = + settings[FG_MEM_IRQ_VOLT_EMPTY].value; + rc = fg_mem_masked_write(chip, settings[FG_MEM_BATT_LOW].address, 0xFF, batt_to_setpoint_8b(settings[FG_MEM_BATT_LOW].value), settings[FG_MEM_BATT_LOW].offset); @@ -6274,20 +8107,41 @@ static int fg_common_hw_init(struct fg_chip *chip) if (fg_debug_mask & FG_STATUS) pr_info("imptr_pulse_slow is %sabled\n", chip->imptr_pulse_slow_en ? "en" : "dis"); + } - rc = fg_mem_read(chip, &val, RSLOW_CFG_REG, 1, RSLOW_CFG_OFFSET, - 0); - if (rc) { - pr_err("unable to read rslow cfg: %d\n", rc); - return rc; - } + rc = fg_mem_read(chip, &val, RSLOW_CFG_REG, 1, RSLOW_CFG_OFFSET, + 0); + if (rc) { + pr_err("unable to read rslow cfg: %d\n", rc); + return rc; + } - if (val & RSLOW_CFG_ON_VAL) - chip->rslow_comp.active = true; + if (val & RSLOW_CFG_ON_VAL) + chip->rslow_comp.active = true; - if (fg_debug_mask & FG_STATUS) - pr_info("rslow_comp active is %sabled\n", - chip->rslow_comp.active ? "en" : "dis"); + if (fg_debug_mask & FG_STATUS) + pr_info("rslow_comp active is %sabled\n", + chip->rslow_comp.active ? "en" : "dis"); + + /* + * Clear bits 0-2 in 0x4B3 and set them again to make empty_soc irq + * trigger again. + */ + rc = fg_mem_masked_write(chip, FG_ALG_SYSCTL_1, EMPTY_SOC_IRQ_MASK, + 0, ALERT_CFG_OFFSET); + if (rc) { + pr_err("failed to write to 0x4B3 rc=%d\n", rc); + return rc; + } + + /* Wait for a FG cycle before enabling empty soc irq configuration */ + msleep(FG_CYCLE_MS); + + rc = fg_mem_masked_write(chip, FG_ALG_SYSCTL_1, EMPTY_SOC_IRQ_MASK, + EMPTY_SOC_IRQ_MASK, ALERT_CFG_OFFSET); + if (rc) { + pr_err("failed to write to 0x4B3 rc=%d\n", rc); + return rc; } return 0; @@ -6414,12 +8268,13 @@ static int fg_hw_init(struct fg_chip *chip) /* Setup workaround flag based on PMIC type */ if (fg_sense_type == INTERNAL_CURRENT_SENSE) chip->wa_flag |= IADC_GAIN_COMP_WA; - if (chip->pmic_revision[REVID_DIG_MAJOR] > 1) + if (chip->pmic_revision[REVID_DIG_MAJOR] >= 1) chip->wa_flag |= USE_CC_SOC_REG; break; case PMI8950: case PMI8937: + case PMI8940: rc = fg_8950_hw_init(chip); /* Setup workaround flag based on PMIC type */ chip->wa_flag |= BCL_HI_POWER_FOR_CHGLED_WA; @@ -6438,12 +8293,223 @@ static int fg_hw_init(struct fg_chip *chip) return rc; } +static int fg_init_iadc_config(struct fg_chip *chip) +{ + u8 reg[2]; + int rc; + + /* read default gain config */ + rc = fg_mem_read(chip, reg, K_VCOR_REG, 2, DEF_GAIN_OFFSET, 0); + if (rc) { + pr_err("Failed to read default gain rc=%d\n", rc); + return rc; + } + + if (reg[1] || reg[0]) { + /* + * Default gain register has valid value: + * - write to gain register. + */ + rc = fg_mem_write(chip, reg, GAIN_REG, 2, + GAIN_OFFSET, 0); + if (rc) { + pr_err("Failed to write gain rc=%d\n", rc); + return rc; + } + } else { + /* + * Default gain register is invalid: + * - read gain register for default gain value + * - write to default gain register. + */ + rc = fg_mem_read(chip, reg, GAIN_REG, 2, + GAIN_OFFSET, 0); + if (rc) { + pr_err("Failed to read gain rc=%d\n", rc); + return rc; + } + rc = fg_mem_write(chip, reg, K_VCOR_REG, 2, + DEF_GAIN_OFFSET, 0); + if (rc) { + pr_err("Failed to write default gain rc=%d\n", + rc); + return rc; + } + } + + chip->iadc_comp_data.dfl_gain_reg[0] = reg[0]; + chip->iadc_comp_data.dfl_gain_reg[1] = reg[1]; + chip->iadc_comp_data.dfl_gain = half_float(reg); + + pr_debug("IADC gain initial config reg_val 0x%x%x gain %lld\n", + reg[1], reg[0], chip->iadc_comp_data.dfl_gain); + return 0; +} + +#define EN_WR_FGXCT_PRD BIT(6) +#define EN_RD_FGXCT_PRD BIT(5) +#define FG_RESTART_TIMEOUT_MS 12000 +static void ima_error_recovery_work(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, + struct fg_chip, + ima_error_recovery_work); + bool tried_again = false; + int rc; + u8 buf[4] = {0, 0, 0, 0}; + + fg_stay_awake(&chip->fg_reset_wakeup_source); + mutex_lock(&chip->ima_recovery_lock); + if (!chip->ima_error_handling) { + pr_err("Scheduled by mistake?\n"); + mutex_unlock(&chip->ima_recovery_lock); + fg_relax(&chip->fg_reset_wakeup_source); + return; + } + + /* + * SOC should be read and used until the error recovery completes. + * Without this, there could be a fluctuation in SOC values notified + * to the userspace. + */ + chip->use_last_soc = true; + + /* Block SRAM access till FG reset is complete */ + chip->block_sram_access = true; + + /* Release the mutex to avoid deadlock while cancelling the works */ + mutex_unlock(&chip->ima_recovery_lock); + + /* Cancel all the works */ + fg_cancel_all_works(chip); + + if (fg_debug_mask & FG_STATUS) + pr_info("last_soc: %d\n", chip->last_soc); + + mutex_lock(&chip->ima_recovery_lock); + /* Acquire IMA access forcibly from FG ALG */ + rc = fg_masked_write(chip, chip->mem_base + MEM_INTF_IMA_CFG, + EN_WR_FGXCT_PRD | EN_RD_FGXCT_PRD, + EN_WR_FGXCT_PRD | EN_RD_FGXCT_PRD, 1); + if (rc) { + pr_err("Error in writing to IMA_CFG, rc=%d\n", rc); + goto out; + } + + /* Release the IMA access now so that FG reset can go through */ + rc = fg_masked_write(chip, chip->mem_base + MEM_INTF_IMA_CFG, + EN_WR_FGXCT_PRD | EN_RD_FGXCT_PRD, 0, 1); + if (rc) { + pr_err("Error in writing to IMA_CFG, rc=%d\n", rc); + goto out; + } + + if (fg_debug_mask & FG_STATUS) + pr_info("resetting FG\n"); + + /* Assert FG reset */ + rc = fg_reset(chip, true); + if (rc) { + pr_err("Couldn't reset FG\n"); + goto out; + } + + /* Wait for a small time before deasserting FG reset */ + msleep(100); + + if (fg_debug_mask & FG_STATUS) + pr_info("clearing FG from reset\n"); + + /* Deassert FG reset */ + rc = fg_reset(chip, false); + if (rc) { + pr_err("Couldn't clear FG reset\n"); + goto out; + } + + /* Wait for at least a FG cycle before doing SRAM access */ + msleep(2000); + + chip->block_sram_access = false; + + if (!chip->init_done) { + schedule_work(&chip->init_work); + goto wait; + } + + if (fg_debug_mask & FG_STATUS) + pr_info("Calling hw_init\n"); + + /* + * Once FG is reset, everything in SRAM will be wiped out. Redo + * hw_init, update jeita settings etc., again to make sure all + * the settings got restored again. + */ + rc = fg_hw_init(chip); + if (rc) { + pr_err("Error in hw_init, rc=%d\n", rc); + goto out; + } + + update_jeita_setting(&chip->update_jeita_setting.work); + + if (chip->wa_flag & IADC_GAIN_COMP_WA) { + rc = fg_init_iadc_config(chip); + if (rc) + goto out; + } + + if (fg_debug_mask & FG_STATUS) + pr_info("loading battery profile\n"); + if (!chip->use_otp_profile) { + chip->battery_missing = true; + chip->profile_loaded = false; + chip->soc_reporting_ready = false; + chip->batt_type = default_batt_type; + fg_handle_battery_insertion(chip); + } + +wait: + rc = wait_for_completion_interruptible_timeout(&chip->fg_reset_done, + msecs_to_jiffies(FG_RESTART_TIMEOUT_MS)); + + /* If we were interrupted wait again one more time. */ + if (rc == -ERESTARTSYS && !tried_again) { + tried_again = true; + pr_debug("interrupted, waiting again\n"); + goto wait; + } else if (rc <= 0) { + pr_err("fg_restart taking long time rc=%d\n", rc); + goto out; + } + + rc = fg_mem_write(chip, buf, fg_data[FG_DATA_VINT_ERR].address, + fg_data[FG_DATA_VINT_ERR].len, + fg_data[FG_DATA_VINT_ERR].offset, 0); + if (rc < 0) + pr_err("Error in clearing VACT_INT_ERR, rc=%d\n", rc); + + if (fg_debug_mask & FG_STATUS) + pr_info("IMA error recovery done...\n"); +out: + fg_restore_soc(chip); + fg_restore_cc_soc(chip); + fg_enable_irqs(chip, true); + update_sram_data_work(&chip->update_sram_data.work); + update_temp_data(&chip->update_temp_work.work); + schedule_delayed_work(&chip->check_sanity_work, + msecs_to_jiffies(1000)); + chip->ima_error_handling = false; + mutex_unlock(&chip->ima_recovery_lock); + fg_relax(&chip->fg_reset_wakeup_source); +} + #define DIG_MINOR 0x0 #define DIG_MAJOR 0x1 #define ANA_MINOR 0x2 #define ANA_MAJOR 0x3 #define IACS_INTR_SRC_SLCT BIT(3) -static int fg_setup_memif_offset(struct fg_chip *chip) +static int fg_memif_init(struct fg_chip *chip) { int rc; @@ -6464,7 +8530,7 @@ static int fg_setup_memif_offset(struct fg_chip *chip) break; default: pr_err("Digital Major rev=%d not supported\n", - chip->revision[DIG_MAJOR]); + chip->revision[DIG_MAJOR]); return -EINVAL; } @@ -6481,6 +8547,13 @@ static int fg_setup_memif_offset(struct fg_chip *chip) pr_err("failed to configure interrupt source %d\n", rc); return rc; } + + /* check for error condition */ + rc = fg_check_ima_exception(chip, true); + if (rc) { + pr_err("Error in clearing IMA exception rc=%d", rc); + return rc; + } } return 0; @@ -6515,6 +8588,7 @@ static int fg_detect_pmic_type(struct fg_chip *chip) case PMI8950: case PMI8937: case PMI8996: + case PMI8940: chip->pmic_subtype = pmic_rev_id->pmic_subtype; chip->pmic_revision[REVID_RESERVED] = pmic_rev_id->rev1; chip->pmic_revision[REVID_VARIANT] = pmic_rev_id->rev2; @@ -6531,10 +8605,8 @@ static int fg_detect_pmic_type(struct fg_chip *chip) } #define INIT_JEITA_DELAY_MS 1000 - static void delayed_init_work(struct work_struct *work) { - u8 reg[2]; int rc; struct fg_chip *chip = container_of(work, struct fg_chip, @@ -6546,6 +8618,14 @@ static void delayed_init_work(struct work_struct *work) rc = fg_hw_init(chip); if (rc) { pr_err("failed to hw init rc = %d\n", rc); + if (!chip->init_done && chip->ima_supported) { + rc = fg_check_alg_status(chip); + if (rc && rc != -EBUSY) + pr_err("Couldn't check FG ALG status, rc=%d\n", + rc); + fg_mem_release(chip); + return; + } fg_mem_release(chip); fg_cleanup(chip); return; @@ -6566,57 +8646,19 @@ static void delayed_init_work(struct work_struct *work) if (!chip->use_otp_profile) schedule_delayed_work(&chip->batt_profile_init, 0); + if (chip->ima_supported && fg_reset_on_lockup) + schedule_delayed_work(&chip->check_sanity_work, + msecs_to_jiffies(1000)); + if (chip->wa_flag & IADC_GAIN_COMP_WA) { - /* read default gain config */ - rc = fg_mem_read(chip, reg, K_VCOR_REG, 2, DEF_GAIN_OFFSET, 0); - if (rc) { - pr_err("Failed to read default gain rc=%d\n", rc); + rc = fg_init_iadc_config(chip); + if (rc) goto done; - } - - if (reg[1] || reg[0]) { - /* - * Default gain register has valid value: - * - write to gain register. - */ - rc = fg_mem_write(chip, reg, GAIN_REG, 2, - GAIN_OFFSET, 0); - if (rc) { - pr_err("Failed to write gain rc=%d\n", rc); - goto done; - } - } else { - /* - * Default gain register is invalid: - * - read gain register for default gain value - * - write to default gain register. - */ - rc = fg_mem_read(chip, reg, GAIN_REG, 2, - GAIN_OFFSET, 0); - if (rc) { - pr_err("Failed to read gain rc=%d\n", rc); - goto done; - } - rc = fg_mem_write(chip, reg, K_VCOR_REG, 2, - DEF_GAIN_OFFSET, 0); - if (rc) { - pr_err("Failed to write default gain rc=%d\n", - rc); - goto done; - } - } - - chip->iadc_comp_data.dfl_gain_reg[0] = reg[0]; - chip->iadc_comp_data.dfl_gain_reg[1] = reg[1]; - chip->iadc_comp_data.dfl_gain = half_float(reg); - chip->input_present = is_input_present(chip); - chip->otg_present = is_otg_present(chip); - chip->init_done = true; - - pr_debug("IADC gain initial config reg_val 0x%x%x gain %lld\n", - reg[1], reg[0], chip->iadc_comp_data.dfl_gain); } + chip->input_present = is_input_present(chip); + chip->otg_present = is_otg_present(chip); + chip->init_done = true; pr_debug("FG: HW_init success\n"); return; @@ -6675,16 +8717,30 @@ static int fg_probe(struct platform_device *pdev) "qpnp_fg_cap_learning"); wakeup_source_init(&chip->esr_extract_wakeup_source.source, "qpnp_fg_esr_extract"); + wakeup_source_init(&chip->slope_limit_wakeup_source.source, + "qpnp_fg_slope_limit"); + wakeup_source_init(&chip->dischg_gain_wakeup_source.source, + "qpnp_fg_dischg_gain"); + wakeup_source_init(&chip->fg_reset_wakeup_source.source, + "qpnp_fg_reset"); + wakeup_source_init(&chip->cc_soc_wakeup_source.source, + "qpnp_fg_cc_soc"); + wakeup_source_init(&chip->sanity_wakeup_source.source, + "qpnp_fg_sanity_check"); + spin_lock_init(&chip->sec_access_lock); mutex_init(&chip->rw_lock); mutex_init(&chip->cyc_ctr.lock); mutex_init(&chip->learning_data.learning_lock); mutex_init(&chip->rslow_comp.lock); mutex_init(&chip->sysfs_restart_lock); + mutex_init(&chip->ima_recovery_lock); INIT_DELAYED_WORK(&chip->update_jeita_setting, update_jeita_setting); INIT_DELAYED_WORK(&chip->update_sram_data, update_sram_data_work); INIT_DELAYED_WORK(&chip->update_temp_work, update_temp_data); INIT_DELAYED_WORK(&chip->check_empty_work, check_empty_work); INIT_DELAYED_WORK(&chip->batt_profile_init, batt_profile_init); + INIT_DELAYED_WORK(&chip->check_sanity_work, check_sanity_work); + INIT_WORK(&chip->ima_error_recovery_work, ima_error_recovery_work); INIT_WORK(&chip->rslow_comp_work, rslow_comp_work); INIT_WORK(&chip->fg_cap_learning_work, fg_cap_learning_work); INIT_WORK(&chip->dump_sram, dump_sram); @@ -6699,13 +8755,19 @@ static int fg_probe(struct platform_device *pdev) INIT_WORK(&chip->gain_comp_work, iadc_gain_comp_work); INIT_WORK(&chip->bcl_hi_power_work, bcl_hi_power_work); INIT_WORK(&chip->esr_extract_config_work, esr_extract_config_work); + INIT_WORK(&chip->slope_limiter_work, slope_limiter_work); + INIT_WORK(&chip->dischg_gain_work, discharge_gain_work); + INIT_WORK(&chip->cc_soc_store_work, cc_soc_store_work); alarm_init(&chip->fg_cap_learning_alarm, ALARM_BOOTTIME, fg_cap_learning_alarm_cb); + alarm_init(&chip->hard_jeita_alarm, ALARM_BOOTTIME, + fg_hard_jeita_alarm_cb); init_completion(&chip->sram_access_granted); init_completion(&chip->sram_access_revoked); complete_all(&chip->sram_access_revoked); init_completion(&chip->batt_id_avail); init_completion(&chip->first_soc_done); + init_completion(&chip->fg_reset_done); dev_set_drvdata(&pdev->dev, chip); if (of_get_available_child_count(pdev->dev.of_node) == 0) { @@ -6763,7 +8825,7 @@ static int fg_probe(struct platform_device *pdev) return rc; } - rc = fg_setup_memif_offset(chip); + rc = fg_memif_init(chip); if (rc) { pr_err("Unable to setup mem_if offsets rc=%d\n", rc); goto of_init_fail; @@ -6834,10 +8896,18 @@ static int fg_probe(struct platform_device *pdev) rc = fg_dfs_create(chip); if (rc < 0) { pr_err("failed to create debugfs rc = %d\n", rc); - goto cancel_work; + goto power_supply_unregister; } } + /* Fake temperature till the actual temperature is read */ + chip->last_good_temp = 250; + + /* Initialize batt_info variables */ + chip->batt_range_ocv = &fg_batt_valid_ocv; + chip->batt_range_pct = &fg_batt_range_pct; + memset(chip->batt_info, INT_MAX, sizeof(chip->batt_info)); + schedule_work(&chip->init_work); pr_info("FG Probe success - FG Revision DIG:%d.%d ANA:%d.%d PMIC subtype=%d\n", @@ -6847,32 +8917,17 @@ static int fg_probe(struct platform_device *pdev) return rc; +power_supply_unregister: + power_supply_unregister(chip->bms_psy); cancel_work: - cancel_delayed_work_sync(&chip->update_jeita_setting); - cancel_delayed_work_sync(&chip->update_sram_data); - cancel_delayed_work_sync(&chip->update_temp_work); - cancel_delayed_work_sync(&chip->check_empty_work); - cancel_delayed_work_sync(&chip->batt_profile_init); - alarm_try_to_cancel(&chip->fg_cap_learning_alarm); - cancel_work_sync(&chip->set_resume_soc_work); - cancel_work_sync(&chip->fg_cap_learning_work); - cancel_work_sync(&chip->dump_sram); - cancel_work_sync(&chip->status_change_work); - cancel_work_sync(&chip->cycle_count_work); - cancel_work_sync(&chip->update_esr_work); - cancel_work_sync(&chip->rslow_comp_work); - cancel_work_sync(&chip->sysfs_restart_work); - cancel_work_sync(&chip->gain_comp_work); - cancel_work_sync(&chip->init_work); - cancel_work_sync(&chip->charge_full_work); - cancel_work_sync(&chip->bcl_hi_power_work); - cancel_work_sync(&chip->esr_extract_config_work); + fg_cancel_all_works(chip); of_init_fail: mutex_destroy(&chip->rslow_comp.lock); mutex_destroy(&chip->rw_lock); mutex_destroy(&chip->cyc_ctr.lock); mutex_destroy(&chip->learning_data.learning_lock); mutex_destroy(&chip->sysfs_restart_lock); + mutex_destroy(&chip->ima_recovery_lock); wakeup_source_trash(&chip->resume_soc_wakeup_source.source); wakeup_source_trash(&chip->empty_check_wakeup_source.source); wakeup_source_trash(&chip->memif_wakeup_source.source); @@ -6882,6 +8937,11 @@ of_init_fail: wakeup_source_trash(&chip->gain_comp_wakeup_source.source); wakeup_source_trash(&chip->capacity_learning_wakeup_source.source); wakeup_source_trash(&chip->esr_extract_wakeup_source.source); + wakeup_source_trash(&chip->slope_limit_wakeup_source.source); + wakeup_source_trash(&chip->dischg_gain_wakeup_source.source); + wakeup_source_trash(&chip->fg_reset_wakeup_source.source); + wakeup_source_trash(&chip->cc_soc_wakeup_source.source); + wakeup_source_trash(&chip->sanity_wakeup_source.source); return rc; } @@ -6938,11 +8998,103 @@ static int fg_resume(struct device *dev) return 0; } +static void fg_check_ima_idle(struct fg_chip *chip) +{ + bool rif_mem_sts = true; + int rc, time_count = 0; + + mutex_lock(&chip->rw_lock); + /* Make sure IMA is idle */ + while (1) { + rc = fg_check_rif_mem_access(chip, &rif_mem_sts); + if (rc) + break; + + if (!rif_mem_sts) + break; + + if (time_count > 4) { + pr_err("Waited for ~16ms polling RIF_MEM_ACCESS_REQ\n"); + fg_run_iacs_clear_sequence(chip); + break; + } + + /* Wait for 4ms before reading RIF_MEM_ACCESS_REQ again */ + usleep_range(4000, 4100); + time_count++; + } + mutex_unlock(&chip->rw_lock); +} + +static void fg_shutdown(struct platform_device *pdev) +{ + struct fg_chip *chip = dev_get_drvdata(&pdev->dev); + + if (fg_debug_mask & FG_STATUS) + pr_emerg("FG shutdown started\n"); + fg_cancel_all_works(chip); + fg_check_ima_idle(chip); + chip->fg_shutdown = true; + if (fg_debug_mask & FG_STATUS) + pr_emerg("FG shutdown complete\n"); +} + static const struct dev_pm_ops qpnp_fg_pm_ops = { .suspend = fg_suspend, .resume = fg_resume, }; +static int fg_reset_lockup_set(const char *val, const struct kernel_param *kp) +{ + int rc; + struct power_supply *bms_psy; + struct fg_chip *chip; + int old_val = fg_reset_on_lockup; + + rc = param_set_int(val, kp); + if (rc) { + pr_err("Unable to set fg_reset_on_lockup: %d\n", rc); + return rc; + } + + if (fg_reset_on_lockup != 0 && fg_reset_on_lockup != 1) { + pr_err("Bad value %d\n", fg_reset_on_lockup); + fg_reset_on_lockup = old_val; + return -EINVAL; + } + + bms_psy = power_supply_get_by_name("bms"); + if (!bms_psy) { + pr_err("bms psy not found\n"); + return 0; + } + + chip = power_supply_get_drvdata(bms_psy); + if (!chip->ima_supported) { + pr_err("Cannot set this for non-IMA supported FG\n"); + fg_reset_on_lockup = old_val; + return -EINVAL; + } + + if (fg_debug_mask & FG_STATUS) + pr_info("fg_reset_on_lockup set to %d\n", fg_reset_on_lockup); + + if (fg_reset_on_lockup) + schedule_delayed_work(&chip->check_sanity_work, + msecs_to_jiffies(1000)); + else + cancel_delayed_work_sync(&chip->check_sanity_work); + + return rc; +} + +static struct kernel_param_ops fg_reset_ops = { + .set = fg_reset_lockup_set, + .get = param_get_int, +}; + +module_param_cb(reset_on_lockup, &fg_reset_ops, &fg_reset_on_lockup, 0644); + static int fg_sense_type_set(const char *val, const struct kernel_param *kp) { int rc; @@ -7025,6 +9177,7 @@ static struct platform_driver fg_driver = { }, .probe = fg_probe, .remove = fg_remove, + .shutdown = fg_shutdown, }; static int __init fg_init(void) |
