| /* Copyright (c) 2014-2015, 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 |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #define pr_fmt(fmt) "FG: %s: " fmt, __func__ |
| |
| #include <linux/atomic.h> |
| #include <linux/delay.h> |
| #include <linux/kernel.h> |
| #include <linux/of.h> |
| #include <linux/rtc.h> |
| #include <linux/err.h> |
| #include <linux/debugfs.h> |
| #include <linux/slab.h> |
| #include <linux/uaccess.h> |
| #include <linux/init.h> |
| #include <linux/spmi.h> |
| #include <linux/of_irq.h> |
| #include <linux/interrupt.h> |
| #include <linux/bitops.h> |
| #include <linux/types.h> |
| #include <linux/module.h> |
| #include <linux/ktime.h> |
| #include <linux/power_supply.h> |
| #include <linux/of_batterydata.h> |
| #include <linux/string_helpers.h> |
| #include <linux/alarmtimer.h> |
| #include <linux/qpnp-revid.h> |
| |
| /* Register offsets */ |
| |
| /* Interrupt offsets */ |
| #define INT_RT_STS(base) (base + 0x10) |
| #define INT_EN_CLR(base) (base + 0x16) |
| |
| /* SPMI Register offsets */ |
| #define SOC_MONOTONIC_SOC 0x09 |
| #define OTP_CFG1 0xE2 |
| #define SOC_BOOT_MOD 0x50 |
| #define SOC_RESTART 0x51 |
| |
| #define REG_OFFSET_PERP_SUBTYPE 0x05 |
| |
| /* RAM register offsets */ |
| #define RAM_OFFSET 0x400 |
| |
| /* Bit/Mask definitions */ |
| #define FULL_PERCENT 0xFF |
| #define MAX_TRIES_SOC 5 |
| #define MA_MV_BIT_RES 39 |
| #define MSB_SIGN BIT(7) |
| #define IBAT_VBAT_MASK 0x7F |
| #define NO_OTP_PROF_RELOAD BIT(6) |
| #define REDO_FIRST_ESTIMATE BIT(3) |
| #define RESTART_GO BIT(0) |
| |
| /* SUBTYPE definitions */ |
| #define FG_SOC 0x9 |
| #define FG_BATT 0xA |
| #define FG_ADC 0xB |
| #define FG_MEMIF 0xC |
| |
| #define QPNP_FG_DEV_NAME "qcom,qpnp-fg" |
| #define MEM_IF_TIMEOUT_MS 5000 |
| #define BUCKET_COUNT 8 |
| #define BUCKET_SOC_PCT (256 / BUCKET_COUNT) |
| |
| #define BCL_MA_TO_ADC(_current, _adc_val) { \ |
| _adc_val = (u8)((_current) * 100 / 976); \ |
| } |
| |
| /* Debug Flag Definitions */ |
| enum { |
| FG_SPMI_DEBUG_WRITES = BIT(0), /* Show SPMI writes */ |
| FG_SPMI_DEBUG_READS = BIT(1), /* Show SPMI reads */ |
| FG_IRQS = BIT(2), /* Show interrupts */ |
| FG_MEM_DEBUG_WRITES = BIT(3), /* Show SRAM writes */ |
| FG_MEM_DEBUG_READS = BIT(4), /* Show SRAM reads */ |
| FG_POWER_SUPPLY = BIT(5), /* Show POWER_SUPPLY */ |
| FG_STATUS = BIT(6), /* Show FG status changes */ |
| FG_AGING = BIT(7), /* Show FG aging algorithm */ |
| }; |
| |
| enum dig_major { |
| DIG_REV_8994_1 = 0x1, |
| DIG_REV_8994_2 = 0x2, |
| }; |
| |
| enum pmic_subtype { |
| PMI8994 = 10, |
| PMI8950 = 17, |
| }; |
| |
| struct fg_mem_setting { |
| u16 address; |
| u8 offset; |
| int value; |
| }; |
| |
| struct fg_mem_data { |
| u16 address; |
| u8 offset; |
| unsigned int len; |
| int value; |
| }; |
| |
| struct fg_learning_data { |
| int64_t cc_uah; |
| int64_t learned_cc_uah; |
| bool active; |
| bool feedback_on; |
| struct mutex learning_lock; |
| ktime_t time_stamp; |
| /* configuration properties */ |
| int max_start_soc; |
| int max_increment; |
| int max_decrement; |
| int min_temp; |
| int max_temp; |
| int vbat_est_thr_uv; |
| }; |
| |
| struct fg_rslow_data { |
| u8 rslow_cfg; |
| u8 rslow_thr; |
| u8 rs_to_rslow[2]; |
| u8 rslow_comp[4]; |
| uint32_t chg_rs_to_rslow; |
| uint32_t chg_rslow_comp_c1; |
| uint32_t chg_rslow_comp_c2; |
| uint32_t chg_rslow_comp_thr; |
| bool active; |
| struct mutex lock; |
| }; |
| |
| struct fg_cyc_ctr_data { |
| bool en; |
| bool started[BUCKET_COUNT]; |
| u16 count[BUCKET_COUNT]; |
| u8 last_soc[BUCKET_COUNT]; |
| int id; |
| struct mutex lock; |
| }; |
| |
| /* FG_MEMIF setting index */ |
| enum fg_mem_setting_index { |
| FG_MEM_SOFT_COLD = 0, |
| FG_MEM_SOFT_HOT, |
| FG_MEM_HARD_COLD, |
| FG_MEM_HARD_HOT, |
| FG_MEM_RESUME_SOC, |
| FG_MEM_BCL_LM_THRESHOLD, |
| FG_MEM_BCL_MH_THRESHOLD, |
| FG_MEM_TERM_CURRENT, |
| FG_MEM_CHG_TERM_CURRENT, |
| FG_MEM_IRQ_VOLT_EMPTY, |
| FG_MEM_CUTOFF_VOLTAGE, |
| FG_MEM_VBAT_EST_DIFF, |
| FG_MEM_DELTA_SOC, |
| FG_MEM_SOC_MAX, |
| FG_MEM_SOC_MIN, |
| FG_MEM_BATT_LOW, |
| FG_MEM_SETTING_MAX, |
| }; |
| |
| /* FG_MEMIF data index */ |
| enum fg_mem_data_index { |
| FG_DATA_BATT_TEMP = 0, |
| FG_DATA_OCV, |
| FG_DATA_VOLTAGE, |
| FG_DATA_CURRENT, |
| FG_DATA_BATT_ESR, |
| FG_DATA_BATT_ESR_COUNT, |
| FG_DATA_BATT_SOC, |
| FG_DATA_CC_CHARGE, |
| FG_DATA_VINT_ERR, |
| FG_DATA_CPRED_VOLTAGE, |
| /* values below this only gets read once per profile reload */ |
| FG_DATA_BATT_ID, |
| FG_DATA_BATT_ID_INFO, |
| FG_DATA_MAX, |
| }; |
| |
| #define SETTING(_idx, _address, _offset, _value) \ |
| [FG_MEM_##_idx] = { \ |
| .address = _address, \ |
| .offset = _offset, \ |
| .value = _value, \ |
| } \ |
| |
| /* modify the battery warm and hot temperature threshold */ |
| /* as our battery warm/hot temperature is 45 and 60 degree */ |
| static struct fg_mem_setting settings[FG_MEM_SETTING_MAX] = { |
| /* ID Address, Offset, Value*/ |
| SETTING(SOFT_COLD, 0x454, 0, 100), |
| SETTING(SOFT_HOT, 0x454, 1, 450), |
| SETTING(HARD_COLD, 0x454, 2, 50), |
| SETTING(HARD_HOT, 0x454, 3, 600), |
| SETTING(RESUME_SOC, 0x45C, 1, 0), |
| SETTING(BCL_LM_THRESHOLD, 0x47C, 2, 50), |
| SETTING(BCL_MH_THRESHOLD, 0x47C, 3, 752), |
| SETTING(TERM_CURRENT, 0x40C, 2, 250), |
| SETTING(CHG_TERM_CURRENT, 0x4F8, 2, 250), |
| SETTING(IRQ_VOLT_EMPTY, 0x458, 3, 3100), |
| SETTING(CUTOFF_VOLTAGE, 0x40C, 0, 3200), |
| SETTING(VBAT_EST_DIFF, 0x000, 0, 30), |
| SETTING(DELTA_SOC, 0x450, 3, 1), |
| SETTING(SOC_MAX, 0x458, 1, 85), |
| SETTING(SOC_MIN, 0x458, 2, 15), |
| SETTING(BATT_LOW, 0x458, 0, 4200), |
| }; |
| |
| #define DATA(_idx, _address, _offset, _length, _value) \ |
| [FG_DATA_##_idx] = { \ |
| .address = _address, \ |
| .offset = _offset, \ |
| .len = _length, \ |
| .value = _value, \ |
| } \ |
| |
| static struct fg_mem_data fg_data[FG_DATA_MAX] = { |
| /* ID Address, Offset, Length, Value*/ |
| DATA(BATT_TEMP, 0x550, 2, 2, -EINVAL), |
| DATA(OCV, 0x588, 3, 2, -EINVAL), |
| DATA(VOLTAGE, 0x5CC, 1, 2, -EINVAL), |
| DATA(CURRENT, 0x5CC, 3, 2, -EINVAL), |
| DATA(BATT_ESR, 0x554, 2, 2, -EINVAL), |
| DATA(BATT_ESR_COUNT, 0x558, 2, 2, -EINVAL), |
| DATA(BATT_SOC, 0x56C, 1, 3, -EINVAL), |
| DATA(CC_CHARGE, 0x570, 0, 4, -EINVAL), |
| DATA(VINT_ERR, 0x560, 0, 4, -EINVAL), |
| DATA(CPRED_VOLTAGE, 0x540, 0, 2, -EINVAL), |
| DATA(BATT_ID, 0x594, 1, 1, -EINVAL), |
| DATA(BATT_ID_INFO, 0x594, 3, 1, -EINVAL), |
| }; |
| |
| static int fg_debug_mask; |
| module_param_named( |
| debug_mask, fg_debug_mask, int, S_IRUSR | S_IWUSR |
| ); |
| |
| static int fg_sense_type = -EINVAL; |
| static int fg_restart; |
| |
| static int fg_est_dump; |
| module_param_named( |
| first_est_dump, fg_est_dump, int, S_IRUSR | S_IWUSR |
| ); |
| |
| static char *fg_batt_type; |
| module_param_named( |
| battery_type, fg_batt_type, charp, S_IRUSR | S_IWUSR |
| ); |
| |
| static int fg_sram_update_period_ms = 30000; |
| module_param_named( |
| sram_update_period_ms, fg_sram_update_period_ms, int, S_IRUSR | S_IWUSR |
| ); |
| |
| struct fg_irq { |
| int irq; |
| unsigned long disabled; |
| }; |
| |
| enum fg_soc_irq { |
| HIGH_SOC, |
| LOW_SOC, |
| FULL_SOC, |
| EMPTY_SOC, |
| DELTA_SOC, |
| FIRST_EST_DONE, |
| SW_FALLBK_OCV, |
| SW_FALLBK_NEW_BATTRT_STS, |
| FG_SOC_IRQ_COUNT, |
| }; |
| |
| enum fg_batt_irq { |
| JEITA_SOFT_COLD, |
| JEITA_SOFT_HOT, |
| VBATT_LOW, |
| BATT_IDENTIFIED, |
| BATT_ID_REQ, |
| BATTERY_UNKNOWN, |
| BATT_MISSING, |
| BATT_MATCH, |
| FG_BATT_IRQ_COUNT, |
| }; |
| |
| enum fg_mem_if_irq { |
| FG_MEM_AVAIL, |
| TA_RCVRY_SUG, |
| FG_MEM_IF_IRQ_COUNT, |
| }; |
| |
| enum fg_batt_aging_mode { |
| FG_AGING_NONE, |
| FG_AGING_ESR, |
| FG_AGING_CC, |
| }; |
| |
| enum register_type { |
| MEM_INTF_CFG, |
| MEM_INTF_CTL, |
| MEM_INTF_ADDR_LSB, |
| MEM_INTF_RD_DATA0, |
| MEM_INTF_WR_DATA0, |
| MAX_ADDRESS, |
| }; |
| |
| struct register_offset { |
| u16 address[MAX_ADDRESS]; |
| }; |
| |
| static struct register_offset offset[] = { |
| [0] = { |
| /* CFG CTL LSB RD0 WD0 */ |
| .address = {0x40, 0x41, 0x42, 0x4C, 0x48}, |
| }, |
| }; |
| |
| #define MEM_INTF_CFG(chip) \ |
| ((chip)->mem_base + (chip)->offset[MEM_INTF_CFG]) |
| #define MEM_INTF_CTL(chip) \ |
| ((chip)->mem_base + (chip)->offset[MEM_INTF_CTL]) |
| #define MEM_INTF_ADDR_LSB(chip) \ |
| ((chip)->mem_base + (chip)->offset[MEM_INTF_ADDR_LSB]) |
| #define MEM_INTF_RD_DATA0(chip) \ |
| ((chip)->mem_base + (chip)->offset[MEM_INTF_RD_DATA0]) |
| #define MEM_INTF_WR_DATA0(chip) \ |
| ((chip)->mem_base + (chip)->offset[MEM_INTF_WR_DATA0]) |
| |
| struct fg_wakeup_source { |
| struct wakeup_source source; |
| unsigned long enabled; |
| }; |
| |
| static void fg_stay_awake(struct fg_wakeup_source *source) |
| { |
| if (!__test_and_set_bit(0, &source->enabled)) { |
| __pm_stay_awake(&source->source); |
| pr_debug("enabled source %s\n", source->source.name); |
| } |
| } |
| |
| static void fg_relax(struct fg_wakeup_source *source) |
| { |
| if (__test_and_clear_bit(0, &source->enabled)) { |
| __pm_relax(&source->source); |
| pr_debug("disabled source %s\n", source->source.name); |
| } |
| } |
| |
| #define THERMAL_COEFF_N_BYTES 6 |
| struct fg_chip { |
| struct device *dev; |
| struct spmi_device *spmi; |
| u8 pmic_subtype; |
| u8 revision[4]; |
| u16 soc_base; |
| u16 batt_base; |
| u16 mem_base; |
| u16 vbat_adc_addr; |
| u16 ibat_adc_addr; |
| u16 tp_rev_addr; |
| atomic_t memif_user_cnt; |
| struct fg_irq soc_irq[FG_SOC_IRQ_COUNT]; |
| struct fg_irq batt_irq[FG_BATT_IRQ_COUNT]; |
| struct fg_irq mem_irq[FG_MEM_IF_IRQ_COUNT]; |
| struct completion sram_access_granted; |
| struct completion sram_access_revoked; |
| struct completion batt_id_avail; |
| struct power_supply bms_psy; |
| struct mutex rw_lock; |
| struct mutex sysfs_restart_lock; |
| struct work_struct batt_profile_init; |
| struct work_struct dump_sram; |
| struct work_struct status_change_work; |
| struct work_struct cycle_count_work; |
| struct work_struct battery_age_work; |
| struct work_struct update_esr_work; |
| struct work_struct set_resume_soc_work; |
| struct work_struct rslow_comp_work; |
| struct work_struct sysfs_restart_work; |
| struct work_struct init_work; |
| struct work_struct charge_full_work; |
| struct power_supply *batt_psy; |
| struct power_supply *usb_psy; |
| struct power_supply *dc_psy; |
| struct fg_wakeup_source memif_wakeup_source; |
| struct fg_wakeup_source profile_wakeup_source; |
| struct fg_wakeup_source empty_check_wakeup_source; |
| struct fg_wakeup_source resume_soc_wakeup_source; |
| bool first_profile_loaded; |
| struct fg_wakeup_source update_temp_wakeup_source; |
| struct fg_wakeup_source update_sram_wakeup_source; |
| bool profile_loaded; |
| bool use_otp_profile; |
| bool battery_missing; |
| bool power_supply_registered; |
| bool sw_rbias_ctrl; |
| bool use_thermal_coefficients; |
| bool esr_strict_filter; |
| bool soc_empty; |
| bool charge_done; |
| bool resume_soc_lowered; |
| bool vbat_low_irq_enabled; |
| bool charge_full; |
| bool hold_soc_while_full; |
| struct delayed_work update_jeita_setting; |
| struct delayed_work update_sram_data; |
| struct delayed_work update_temp_work; |
| struct delayed_work check_empty_work; |
| char *batt_profile; |
| u8 thermal_coefficients[THERMAL_COEFF_N_BYTES]; |
| u32 cc_cv_threshold_mv; |
| unsigned int batt_profile_len; |
| unsigned int batt_max_voltage_uv; |
| const char *batt_type; |
| const char *batt_psy_name; |
| unsigned long last_sram_update_time; |
| unsigned long last_temp_update_time; |
| int64_t ocv_coeffs[12]; |
| int64_t cutoff_voltage; |
| int evaluation_current; |
| int ocv_junction_p1p2; |
| int ocv_junction_p2p3; |
| int nom_cap_uah; |
| int actual_cap_uah; |
| int status; |
| int prev_status; |
| int health; |
| enum fg_batt_aging_mode batt_aging_mode; |
| /* capacity learning */ |
| struct fg_learning_data learning_data; |
| struct alarm fg_cap_learning_alarm; |
| struct work_struct fg_cap_learning_work; |
| /* rslow compensation */ |
| struct fg_rslow_data rslow_comp; |
| /* cycle counter */ |
| struct fg_cyc_ctr_data cyc_ctr; |
| /* interleaved memory access */ |
| u16 *offset; |
| }; |
| |
| /* FG_MEMIF DEBUGFS structures */ |
| #define ADDR_LEN 4 /* 3 byte address + 1 space character */ |
| #define CHARS_PER_ITEM 3 /* Format is 'XX ' */ |
| #define ITEMS_PER_LINE 4 /* 4 data items per line */ |
| #define MAX_LINE_LENGTH (ADDR_LEN + (ITEMS_PER_LINE * CHARS_PER_ITEM) + 1) |
| #define MAX_REG_PER_TRANSACTION (8) |
| |
| static const char *DFS_ROOT_NAME = "fg_memif"; |
| static const mode_t DFS_MODE = S_IRUSR | S_IWUSR; |
| static const char *default_batt_type = "Unknown Battery"; |
| static const char *loading_batt_type = "Loading Battery Data"; |
| static const char *missing_batt_type = "Disconnected Battery"; |
| |
| /* Log buffer */ |
| struct fg_log_buffer { |
| size_t rpos; /* Current 'read' position in buffer */ |
| size_t wpos; /* Current 'write' position in buffer */ |
| size_t len; /* Length of the buffer */ |
| char data[0]; /* Log buffer */ |
| }; |
| |
| /* transaction parameters */ |
| struct fg_trans { |
| u32 cnt; /* Number of bytes to read */ |
| u16 addr; /* 12-bit address in SRAM */ |
| u32 offset; /* Offset of last read data + byte offset */ |
| struct fg_chip *chip; |
| struct fg_log_buffer *log; /* log buffer */ |
| u8 *data; /* fg data that is read */ |
| }; |
| |
| struct fg_dbgfs { |
| u32 cnt; |
| u32 addr; |
| struct fg_chip *chip; |
| struct dentry *root; |
| struct mutex lock; |
| struct debugfs_blob_wrapper help_msg; |
| }; |
| |
| static struct fg_dbgfs dbgfs_data = { |
| .lock = __MUTEX_INITIALIZER(dbgfs_data.lock), |
| .help_msg = { |
| .data = |
| "FG Debug-FS support\n" |
| "\n" |
| "Hierarchy schema:\n" |
| "/sys/kernel/debug/fg_memif\n" |
| " /help -- Static help text\n" |
| " /address -- Starting register address for reads or writes\n" |
| " /count -- Number of registers to read (only used for reads)\n" |
| " /data -- Initiates the SRAM read (formatted output)\n" |
| "\n", |
| }, |
| }; |
| |
| static const struct of_device_id fg_match_table[] = { |
| { .compatible = QPNP_FG_DEV_NAME, }, |
| {} |
| }; |
| |
| static char *fg_supplicants[] = { |
| "battery", |
| "bcl", |
| "fg_adc" |
| }; |
| |
| #define DEBUG_PRINT_BUFFER_SIZE 64 |
| static void fill_string(char *str, size_t str_len, u8 *buf, int buf_len) |
| { |
| int pos = 0; |
| int i; |
| |
| for (i = 0; i < buf_len; i++) { |
| pos += scnprintf(str + pos, str_len - pos, "%02X", buf[i]); |
| if (i < buf_len - 1) |
| pos += scnprintf(str + pos, str_len - pos, " "); |
| } |
| } |
| |
| static int fg_write(struct fg_chip *chip, u8 *val, u16 addr, int len) |
| { |
| int rc = 0; |
| struct spmi_device *spmi = chip->spmi; |
| char str[DEBUG_PRINT_BUFFER_SIZE]; |
| |
| if ((addr & 0xff00) == 0) { |
| pr_err("addr cannot be zero base=0x%02x sid=0x%02x rc=%d\n", |
| addr, spmi->sid, rc); |
| return -EINVAL; |
| } |
| |
| rc = spmi_ext_register_writel(spmi->ctrl, spmi->sid, addr, val, len); |
| if (rc) { |
| pr_err("write failed addr=0x%02x sid=0x%02x rc=%d\n", |
| addr, spmi->sid, rc); |
| return rc; |
| } |
| |
| if (!rc && (fg_debug_mask & FG_SPMI_DEBUG_WRITES)) { |
| str[0] = '\0'; |
| fill_string(str, DEBUG_PRINT_BUFFER_SIZE, val, len); |
| pr_info("write(0x%04X), sid=%d, len=%d; %s\n", |
| addr, spmi->sid, len, str); |
| } |
| |
| return rc; |
| } |
| |
| static int fg_read(struct fg_chip *chip, u8 *val, u16 addr, int len) |
| { |
| int rc = 0; |
| struct spmi_device *spmi = chip->spmi; |
| char str[DEBUG_PRINT_BUFFER_SIZE]; |
| |
| if ((addr & 0xff00) == 0) { |
| pr_err("base cannot be zero base=0x%02x sid=0x%02x rc=%d\n", |
| addr, spmi->sid, rc); |
| return -EINVAL; |
| } |
| |
| rc = spmi_ext_register_readl(spmi->ctrl, spmi->sid, addr, val, len); |
| if (rc) { |
| pr_err("SPMI read failed base=0x%02x sid=0x%02x rc=%d\n", addr, |
| spmi->sid, rc); |
| return rc; |
| } |
| |
| if (!rc && (fg_debug_mask & FG_SPMI_DEBUG_READS)) { |
| str[0] = '\0'; |
| fill_string(str, DEBUG_PRINT_BUFFER_SIZE, val, len); |
| pr_info("read(0x%04x), sid=%d, len=%d; %s\n", |
| addr, spmi->sid, len, str); |
| } |
| |
| return rc; |
| } |
| |
| static int fg_masked_write(struct fg_chip *chip, u16 addr, |
| u8 mask, u8 val, int len) |
| { |
| int rc; |
| u8 reg; |
| |
| rc = fg_read(chip, ®, addr, len); |
| if (rc) { |
| pr_err("spmi read failed: addr=%03X, rc=%d\n", addr, rc); |
| return rc; |
| } |
| pr_debug("addr = 0x%x read 0x%x\n", addr, reg); |
| |
| reg &= ~mask; |
| reg |= val & mask; |
| |
| pr_debug("Writing 0x%x\n", reg); |
| |
| rc = fg_write(chip, ®, addr, len); |
| if (rc) { |
| pr_err("spmi write failed: addr=%03X, rc=%d\n", addr, rc); |
| return rc; |
| } |
| |
| return rc; |
| } |
| |
| #define RIF_MEM_ACCESS_REQ BIT(7) |
| static inline bool fg_check_sram_access(struct fg_chip *chip) |
| { |
| int rc; |
| u8 mem_if_sts; |
| |
| rc = fg_read(chip, &mem_if_sts, INT_RT_STS(chip->mem_base), 1); |
| if (rc) { |
| pr_err("failed to read mem status rc=%d\n", rc); |
| return 0; |
| } |
| |
| if ((mem_if_sts & BIT(FG_MEM_AVAIL)) == 0) |
| return false; |
| |
| rc = fg_read(chip, &mem_if_sts, MEM_INTF_CFG(chip), 1); |
| if (rc) { |
| pr_err("failed to read mem status rc=%d\n", rc); |
| return 0; |
| } |
| |
| if ((mem_if_sts & RIF_MEM_ACCESS_REQ) == 0) |
| return false; |
| |
| return true; |
| } |
| |
| #define RIF_MEM_ACCESS_REQ BIT(7) |
| static inline int fg_assert_sram_access(struct fg_chip *chip) |
| { |
| int rc; |
| u8 mem_if_sts; |
| |
| rc = fg_read(chip, &mem_if_sts, INT_RT_STS(chip->mem_base), 1); |
| if (rc) { |
| pr_err("failed to read mem status rc=%d\n", rc); |
| return rc; |
| } |
| |
| if ((mem_if_sts & BIT(FG_MEM_AVAIL)) == 0) { |
| pr_err("mem_avail not high: %02x\n", mem_if_sts); |
| return -EINVAL; |
| } |
| |
| rc = fg_read(chip, &mem_if_sts, MEM_INTF_CFG(chip), 1); |
| if (rc) { |
| pr_err("failed to read mem status rc=%d\n", rc); |
| return rc; |
| } |
| |
| if ((mem_if_sts & RIF_MEM_ACCESS_REQ) == 0) { |
| pr_err("mem_avail not high: %02x\n", mem_if_sts); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| #define INTF_CTL_BURST BIT(7) |
| #define INTF_CTL_WR_EN BIT(6) |
| static int fg_config_access(struct fg_chip *chip, bool write, |
| bool burst, bool otp) |
| { |
| int rc; |
| u8 intf_ctl = 0; |
| |
| if (otp) { |
| /* Configure OTP access */ |
| rc = fg_masked_write(chip, chip->mem_base + OTP_CFG1, |
| 0xFF, 0x00, 1); |
| if (rc) { |
| pr_err("failed to set OTP cfg\n"); |
| return -EIO; |
| } |
| } |
| |
| intf_ctl = (write ? INTF_CTL_WR_EN : 0) | (burst ? INTF_CTL_BURST : 0); |
| |
| rc = fg_write(chip, &intf_ctl, MEM_INTF_CTL(chip), 1); |
| if (rc) { |
| pr_err("failed to set mem access bit\n"); |
| return -EIO; |
| } |
| |
| return rc; |
| } |
| |
| static int fg_req_and_wait_access(struct fg_chip *chip, int timeout) |
| { |
| int rc = 0, ret = 0; |
| bool tried_again = false; |
| |
| if (!fg_check_sram_access(chip)) { |
| rc = fg_masked_write(chip, MEM_INTF_CFG(chip), |
| RIF_MEM_ACCESS_REQ, RIF_MEM_ACCESS_REQ, 1); |
| if (rc) { |
| pr_err("failed to set mem access bit\n"); |
| return -EIO; |
| } |
| fg_stay_awake(&chip->memif_wakeup_source); |
| } |
| |
| wait: |
| /* Wait for MEM_AVAIL IRQ. */ |
| ret = wait_for_completion_interruptible_timeout( |
| &chip->sram_access_granted, |
| msecs_to_jiffies(timeout)); |
| /* If we were interrupted wait again one more time. */ |
| if (ret == -ERESTARTSYS && !tried_again) { |
| tried_again = true; |
| goto wait; |
| } else if (ret <= 0) { |
| rc = -ETIMEDOUT; |
| pr_err("transaction timed out rc=%d\n", rc); |
| return rc; |
| } |
| |
| return rc; |
| } |
| |
| static int fg_release_access(struct fg_chip *chip) |
| { |
| int rc; |
| |
| rc = fg_masked_write(chip, MEM_INTF_CFG(chip), |
| RIF_MEM_ACCESS_REQ, 0, 1); |
| fg_relax(&chip->memif_wakeup_source); |
| INIT_COMPLETION(chip->sram_access_granted); |
| |
| return rc; |
| } |
| |
| static void fg_release_access_if_necessary(struct fg_chip *chip) |
| { |
| mutex_lock(&chip->rw_lock); |
| if (atomic_sub_return(1, &chip->memif_user_cnt) <= 0) { |
| fg_release_access(chip); |
| } |
| mutex_unlock(&chip->rw_lock); |
| } |
| |
| /* |
| * fg_mem_lock disallows the fuel gauge to release access until it has been |
| * released. |
| * |
| * an equal number of calls must be made to fg_mem_release for the fuel gauge |
| * driver to release the sram access. |
| */ |
| static void fg_mem_lock(struct fg_chip *chip) |
| { |
| mutex_lock(&chip->rw_lock); |
| atomic_add_return(1, &chip->memif_user_cnt); |
| mutex_unlock(&chip->rw_lock); |
| } |
| |
| static void fg_mem_release(struct fg_chip *chip) |
| { |
| fg_release_access_if_necessary(chip); |
| } |
| |
| static int fg_set_ram_addr(struct fg_chip *chip, u16 *address) |
| { |
| int rc; |
| |
| rc = fg_write(chip, (u8 *) address, |
| chip->mem_base + chip->offset[MEM_INTF_ADDR_LSB], 2); |
| if (rc) { |
| pr_err("spmi write failed: addr=%03X, rc=%d\n", |
| chip->mem_base + chip->offset[MEM_INTF_ADDR_LSB], rc); |
| return rc; |
| } |
| |
| return rc; |
| } |
| |
| #define BUF_LEN 4 |
| static int fg_sub_mem_read(struct fg_chip *chip, u8 *val, u16 address, int len, |
| int offset) |
| { |
| int rc, total_len; |
| u8 *rd_data = val; |
| bool otp; |
| char str[DEBUG_PRINT_BUFFER_SIZE]; |
| |
| if (address < RAM_OFFSET) |
| otp = true; |
| |
| rc = fg_config_access(chip, 0, (len > 4), otp); |
| if (rc) |
| return rc; |
| |
| rc = fg_set_ram_addr(chip, &address); |
| if (rc) |
| return rc; |
| |
| if (fg_debug_mask & FG_MEM_DEBUG_READS) |
| pr_info("length %d addr=%02X\n", len, address); |
| |
| total_len = len; |
| while (len > 0) { |
| if (!offset) { |
| rc = fg_read(chip, rd_data, MEM_INTF_RD_DATA0(chip), |
| min(len, BUF_LEN)); |
| } else { |
| rc = fg_read(chip, rd_data, |
| MEM_INTF_RD_DATA0(chip) + offset, |
| min(len, BUF_LEN - offset)); |
| |
| /* manually set address to allow continous reads */ |
| address += BUF_LEN; |
| |
| rc = fg_set_ram_addr(chip, &address); |
| if (rc) |
| return rc; |
| } |
| if (rc) { |
| pr_err("spmi read failed: addr=%03x, rc=%d\n", |
| MEM_INTF_RD_DATA0(chip) + offset, rc); |
| return rc; |
| } |
| rd_data += (BUF_LEN - offset); |
| len -= (BUF_LEN - offset); |
| offset = 0; |
| } |
| |
| if (fg_debug_mask & FG_MEM_DEBUG_READS) { |
| fill_string(str, DEBUG_PRINT_BUFFER_SIZE, val, total_len); |
| pr_info("data: %s\n", str); |
| } |
| return rc; |
| } |
| |
| static int fg_mem_read(struct fg_chip *chip, u8 *val, u16 address, |
| int len, int offset, bool keep_access) |
| { |
| int rc = 0, user_cnt = 0, orig_address = address; |
| |
| if (offset > 3) { |
| pr_err("offset too large %d\n", offset); |
| return -EINVAL; |
| } |
| |
| 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_READS) |
| pr_info("user_cnt %d\n", user_cnt); |
| mutex_lock(&chip->rw_lock); |
| if (!fg_check_sram_access(chip)) { |
| rc = fg_req_and_wait_access(chip, MEM_IF_TIMEOUT_MS); |
| if (rc) |
| goto out; |
| } |
| |
| rc = fg_sub_mem_read(chip, val, address, len, offset); |
| |
| out: |
| user_cnt = atomic_sub_return(1, &chip->memif_user_cnt); |
| if (fg_debug_mask & FG_MEM_DEBUG_READS) |
| pr_info("user_cnt %d\n", user_cnt); |
| |
| fg_assert_sram_access(chip); |
| |
| if (!keep_access && (user_cnt == 0) && !rc) { |
| rc = fg_release_access(chip); |
| if (rc) { |
| pr_err("failed to set mem access bit\n"); |
| rc = -EIO; |
| } |
| } |
| |
| mutex_unlock(&chip->rw_lock); |
| return rc; |
| } |
| |
| static int fg_mem_write(struct fg_chip *chip, u8 *val, u16 address, |
| int len, int offset, bool keep_access) |
| { |
| int rc = 0, user_cnt = 0, sublen; |
| bool access_configured = false; |
| u8 *wr_data = val, word[4]; |
| char str[DEBUG_PRINT_BUFFER_SIZE]; |
| |
| if (address < RAM_OFFSET) |
| return -EINVAL; |
| |
| if (offset > 3) |
| return -EINVAL; |
| |
| address = ((address + offset) / 4) * 4; |
| offset = (address + offset) % 4; |
| |
| user_cnt = atomic_add_return(1, &chip->memif_user_cnt); |
| if (fg_debug_mask & FG_MEM_DEBUG_WRITES) |
| pr_info("user_cnt %d\n", user_cnt); |
| mutex_lock(&chip->rw_lock); |
| if (!fg_check_sram_access(chip)) { |
| rc = fg_req_and_wait_access(chip, MEM_IF_TIMEOUT_MS); |
| if (rc) |
| goto out; |
| } |
| |
| if (fg_debug_mask & FG_MEM_DEBUG_WRITES) { |
| pr_info("length %d addr=%02X offset=%d\n", |
| len, address, offset); |
| fill_string(str, DEBUG_PRINT_BUFFER_SIZE, wr_data, len); |
| pr_info("writing: %s\n", str); |
| } |
| |
| while (len > 0) { |
| if (offset != 0) { |
| sublen = min(4 - offset, len); |
| rc = fg_sub_mem_read(chip, word, address, 4, 0); |
| if (rc) |
| goto out; |
| memcpy(word + offset, wr_data, sublen); |
| /* configure access as burst if more to write */ |
| rc = fg_config_access(chip, 1, (len - sublen) > 0, 0); |
| if (rc) |
| goto out; |
| rc = fg_set_ram_addr(chip, &address); |
| if (rc) |
| goto out; |
| offset = 0; |
| access_configured = true; |
| } else if (len >= 4) { |
| if (!access_configured) { |
| rc = fg_config_access(chip, 1, len > 4, 0); |
| if (rc) |
| goto out; |
| rc = fg_set_ram_addr(chip, &address); |
| if (rc) |
| goto out; |
| access_configured = true; |
| } |
| sublen = 4; |
| memcpy(word, wr_data, 4); |
| } else if (len > 0 && len < 4) { |
| sublen = len; |
| rc = fg_sub_mem_read(chip, word, address, 4, 0); |
| if (rc) |
| goto out; |
| memcpy(word, wr_data, sublen); |
| rc = fg_config_access(chip, 1, 0, 0); |
| if (rc) |
| goto out; |
| rc = fg_set_ram_addr(chip, &address); |
| if (rc) |
| goto out; |
| access_configured = true; |
| } else { |
| pr_err("Invalid length: %d\n", len); |
| break; |
| } |
| rc = fg_write(chip, word, MEM_INTF_WR_DATA0(chip), 4); |
| if (rc) { |
| pr_err("spmi write failed: addr=%03x, rc=%d\n", |
| MEM_INTF_WR_DATA0(chip), rc); |
| goto out; |
| } |
| len -= sublen; |
| wr_data += sublen; |
| address += 4; |
| } |
| |
| out: |
| user_cnt = atomic_sub_return(1, &chip->memif_user_cnt); |
| if (fg_debug_mask & FG_MEM_DEBUG_WRITES) |
| pr_info("user_cnt %d\n", user_cnt); |
| |
| fg_assert_sram_access(chip); |
| |
| if (!keep_access && (user_cnt == 0) && !rc) { |
| rc = fg_release_access(chip); |
| if (rc) { |
| pr_err("failed to set mem access bit\n"); |
| rc = -EIO; |
| } |
| } |
| |
| mutex_unlock(&chip->rw_lock); |
| return rc; |
| } |
| |
| static int fg_mem_masked_write(struct fg_chip *chip, u16 addr, |
| u8 mask, u8 val, u8 offset) |
| { |
| int rc = 0; |
| u8 reg[4]; |
| char str[DEBUG_PRINT_BUFFER_SIZE]; |
| |
| rc = fg_mem_read(chip, reg, addr, 4, 0, 1); |
| if (rc) { |
| pr_err("spmi read failed: addr=%03X, rc=%d\n", addr, rc); |
| return rc; |
| } |
| |
| reg[offset] &= ~mask; |
| reg[offset] |= val & mask; |
| |
| str[0] = '\0'; |
| fill_string(str, DEBUG_PRINT_BUFFER_SIZE, reg, 4); |
| pr_debug("Writing %s address %03x, offset %d\n", str, addr, offset); |
| |
| rc = fg_mem_write(chip, reg, addr, 4, 0, 0); |
| if (rc) { |
| pr_err("spmi write failed: addr=%03X, rc=%d\n", addr, rc); |
| return rc; |
| } |
| |
| return rc; |
| } |
| |
| static int soc_to_setpoint(int soc) |
| { |
| return DIV_ROUND_CLOSEST(soc * 255, 100); |
| } |
| |
| static void batt_to_setpoint_adc(int vbatt_mv, u8 *data) |
| { |
| int val; |
| /* Battery voltage is an offset from 0 V and LSB is 1/2^15. */ |
| 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) |
| { |
| int val; |
| /* Battery voltage is an offset from 2.5 V and LSB is 5/2^9. */ |
| val = (vbatt_mv - 2500) * 512 / 1000; |
| return DIV_ROUND_CLOSEST(val, 5); |
| } |
| |
| static int get_current_time(unsigned long *now_tm_sec) |
| { |
| struct rtc_time tm; |
| struct rtc_device *rtc; |
| int rc; |
| |
| rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE); |
| if (rtc == NULL) { |
| pr_err("%s: unable to open rtc device (%s)\n", |
| __FILE__, CONFIG_RTC_HCTOSYS_DEVICE); |
| return -EINVAL; |
| } |
| |
| rc = rtc_read_time(rtc, &tm); |
| if (rc) { |
| pr_err("Error reading rtc device (%s) : %d\n", |
| CONFIG_RTC_HCTOSYS_DEVICE, rc); |
| goto close_time; |
| } |
| |
| rc = rtc_valid_tm(&tm); |
| if (rc) { |
| pr_err("Invalid RTC time (%s): %d\n", |
| CONFIG_RTC_HCTOSYS_DEVICE, rc); |
| goto close_time; |
| } |
| rtc_tm_to_time(&tm, now_tm_sec); |
| |
| close_time: |
| rtc_class_close(rtc); |
| return rc; |
| } |
| |
| #define BATTERY_SOC_REG 0x56C |
| #define BATTERY_SOC_OFFSET 1 |
| #define FULL_PERCENT_3B 0xFFFFFF |
| static int get_battery_soc_raw(struct fg_chip *chip) |
| { |
| int rc; |
| u8 buffer[3]; |
| |
| rc = fg_mem_read(chip, buffer, BATTERY_SOC_REG, 3, 1, 0); |
| if (rc) { |
| pr_err("Unable to read battery soc: %d\n", rc); |
| return 0; |
| } |
| return (int)(buffer[2] << 16 | buffer[1] << 8 | buffer[0]); |
| } |
| |
| #define COUNTER_IMPTR_REG 0X558 |
| #define COUNTER_PULSE_REG 0X55C |
| #define SOC_FULL_REG 0x564 |
| #define COUNTER_IMPTR_OFFSET 2 |
| #define COUNTER_PULSE_OFFSET 0 |
| #define SOC_FULL_OFFSET 3 |
| #define ESR_PULSE_RECONFIG_SOC 0xFFF971 |
| static int fg_configure_soc(struct fg_chip *chip) |
| { |
| u32 batt_soc; |
| u8 cntr[2] = {0, 0}; |
| int rc = 0; |
| |
| mutex_lock(&chip->rw_lock); |
| atomic_add_return(1, &chip->memif_user_cnt); |
| mutex_unlock(&chip->rw_lock); |
| |
| /* Read Battery SOC */ |
| batt_soc = get_battery_soc_raw(chip); |
| |
| if (batt_soc > ESR_PULSE_RECONFIG_SOC) { |
| if (fg_debug_mask & FG_POWER_SUPPLY) |
| pr_info("Configuring soc registers batt_soc: %x\n", |
| batt_soc); |
| batt_soc = ESR_PULSE_RECONFIG_SOC; |
| rc = fg_mem_write(chip, (u8 *)&batt_soc, BATTERY_SOC_REG, 3, |
| BATTERY_SOC_OFFSET, 1); |
| if (rc) { |
| pr_err("failed to write BATT_SOC rc=%d\n", rc); |
| goto out; |
| } |
| |
| rc = fg_mem_write(chip, (u8 *)&batt_soc, SOC_FULL_REG, 3, |
| SOC_FULL_OFFSET, 1); |
| if (rc) { |
| pr_err("failed to write SOC_FULL rc=%d\n", rc); |
| goto out; |
| } |
| |
| rc = fg_mem_write(chip, cntr, COUNTER_IMPTR_REG, 2, |
| COUNTER_IMPTR_OFFSET, 1); |
| if (rc) { |
| pr_err("failed to write COUNTER_IMPTR rc=%d\n", rc); |
| goto out; |
| } |
| |
| rc = fg_mem_write(chip, cntr, COUNTER_PULSE_REG, 2, |
| COUNTER_PULSE_OFFSET, 0); |
| if (rc) |
| pr_err("failed to write COUNTER_IMPTR rc=%d\n", rc); |
| } |
| out: |
| fg_release_access_if_necessary(chip); |
| return rc; |
| } |
| |
| #define SOC_EMPTY BIT(3) |
| static bool fg_is_batt_empty(struct fg_chip *chip) |
| { |
| u8 fg_soc_sts; |
| int rc; |
| |
| 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); |
| return false; |
| } |
| |
| return (fg_soc_sts & SOC_EMPTY) != 0; |
| } |
| |
| static int get_monotonic_soc_raw(struct fg_chip *chip) |
| { |
| u8 cap[2]; |
| int rc, tries = 0; |
| |
| while (tries < MAX_TRIES_SOC) { |
| rc = fg_read(chip, cap, |
| chip->soc_base + SOC_MONOTONIC_SOC, 2); |
| if (rc) { |
| pr_err("spmi read failed: addr=%03x, rc=%d\n", |
| chip->soc_base + SOC_MONOTONIC_SOC, rc); |
| return rc; |
| } |
| |
| if (cap[0] == cap[1]) |
| break; |
| |
| tries++; |
| } |
| |
| if (tries == MAX_TRIES_SOC) { |
| pr_err("shadow registers do not match\n"); |
| return -EINVAL; |
| } |
| |
| if (fg_debug_mask & FG_POWER_SUPPLY) |
| pr_info_ratelimited("raw: 0x%02x\n", cap[0]); |
| return cap[0]; |
| } |
| |
| #define EMPTY_CAPACITY 0 |
| #define DEFAULT_CAPACITY 50 |
| #define MISSING_CAPACITY 100 |
| #define FULL_CAPACITY 100 |
| #define FULL_SOC_RAW 0xFF |
| static int get_prop_capacity(struct fg_chip *chip) |
| { |
| int msoc; |
| |
| if (chip->battery_missing) |
| return MISSING_CAPACITY; |
| if (!chip->profile_loaded && !chip->use_otp_profile) |
| return DEFAULT_CAPACITY; |
| if (chip->charge_full) |
| return FULL_CAPACITY; |
| if (chip->soc_empty) { |
| if (fg_debug_mask & FG_POWER_SUPPLY) |
| pr_info_ratelimited("capacity: %d, EMPTY\n", |
| EMPTY_CAPACITY); |
| return EMPTY_CAPACITY; |
| } |
| msoc = get_monotonic_soc_raw(chip); |
| if (msoc == 0) |
| return EMPTY_CAPACITY; |
| else if (msoc == FULL_SOC_RAW) |
| return FULL_CAPACITY; |
| return DIV_ROUND_CLOSEST((msoc - 1) * (FULL_CAPACITY - 1), |
| FULL_SOC_RAW - 2) + 1; |
| } |
| |
| #define HIGH_BIAS 3 |
| #define MED_BIAS BIT(1) |
| #define LOW_BIAS BIT(0) |
| static u8 bias_ua[] = { |
| [HIGH_BIAS] = 150, |
| [MED_BIAS] = 15, |
| [LOW_BIAS] = 5, |
| }; |
| |
| static int64_t get_batt_id(unsigned int battery_id_uv, u8 bid_info) |
| { |
| u64 battery_id_ohm; |
| |
| if ((bid_info & 0x3) == 0) { |
| pr_err("can't determine battery id 0x%02x\n", bid_info); |
| return -EINVAL; |
| } |
| |
| battery_id_ohm = div_u64(battery_id_uv, bias_ua[bid_info & 0x3]); |
| |
| return battery_id_ohm; |
| } |
| |
| #define DEFAULT_TEMP_DEGC 250 |
| static int get_sram_prop_now(struct fg_chip *chip, unsigned int type) |
| { |
| if (fg_debug_mask & FG_POWER_SUPPLY) |
| pr_info("addr 0x%02X, offset %d value %d\n", |
| fg_data[type].address, fg_data[type].offset, |
| fg_data[type].value); |
| |
| if (type == FG_DATA_BATT_ID) |
| return get_batt_id(fg_data[type].value, |
| fg_data[FG_DATA_BATT_ID_INFO].value); |
| |
| return fg_data[type].value; |
| } |
| |
| #define MIN_TEMP_DEGC -300 |
| #define MAX_TEMP_DEGC 970 |
| static int get_prop_jeita_temp(struct fg_chip *chip, unsigned int type) |
| { |
| if (fg_debug_mask & FG_POWER_SUPPLY) |
| pr_info("addr 0x%02X, offset %d\n", settings[type].address, |
| settings[type].offset); |
| |
| return settings[type].value; |
| } |
| |
| static int set_prop_jeita_temp(struct fg_chip *chip, |
| unsigned int type, int decidegc) |
| { |
| int rc = 0; |
| |
| if (fg_debug_mask & FG_POWER_SUPPLY) |
| pr_info("addr 0x%02X, offset %d temp%d\n", |
| settings[type].address, |
| settings[type].offset, decidegc); |
| |
| settings[type].value = decidegc; |
| |
| cancel_delayed_work_sync( |
| &chip->update_jeita_setting); |
| schedule_delayed_work( |
| &chip->update_jeita_setting, 0); |
| |
| return rc; |
| } |
| |
| #define EXTERNAL_SENSE_SELECT 0x4AC |
| #define EXTERNAL_SENSE_OFFSET 0x2 |
| #define EXTERNAL_SENSE_BIT BIT(2) |
| static int set_prop_sense_type(struct fg_chip *chip, int ext_sense_type) |
| { |
| int rc; |
| |
| rc = fg_mem_masked_write(chip, EXTERNAL_SENSE_SELECT, |
| EXTERNAL_SENSE_BIT, |
| ext_sense_type ? EXTERNAL_SENSE_BIT : 0, |
| EXTERNAL_SENSE_OFFSET); |
| if (rc) { |
| pr_err("failed to write profile rc=%d\n", rc); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| #define EXPONENT_MASK 0xF800 |
| #define MANTISSA_MASK 0x3FF |
| #define SIGN BIT(10) |
| #define EXPONENT_SHIFT 11 |
| #define MICRO_UNIT 1000000ULL |
| static int64_t float_decode(u16 reg) |
| { |
| int64_t final_val, exponent_val, mantissa_val; |
| int exponent, mantissa, n; |
| bool sign; |
| |
| exponent = (reg & EXPONENT_MASK) >> EXPONENT_SHIFT; |
| mantissa = (reg & MANTISSA_MASK); |
| sign = !!(reg & SIGN); |
| |
| pr_debug("exponent=%d mantissa=%d sign=%d\n", exponent, mantissa, sign); |
| |
| mantissa_val = mantissa * MICRO_UNIT; |
| |
| n = exponent - 15; |
| if (n < 0) |
| exponent_val = MICRO_UNIT >> -n; |
| else |
| exponent_val = MICRO_UNIT << n; |
| |
| n = n - 10; |
| if (n < 0) |
| mantissa_val >>= -n; |
| else |
| mantissa_val <<= n; |
| |
| final_val = exponent_val + mantissa_val; |
| |
| if (sign) |
| final_val *= -1; |
| |
| return final_val; |
| } |
| |
| #define MIN_HALFFLOAT_EXP_N -15 |
| #define MAX_HALFFLOAT_EXP_N 16 |
| static int log2_floor(int64_t uval) |
| { |
| int n = 0; |
| int64_t i = MICRO_UNIT; |
| |
| if (uval > i) { |
| while (uval > i && n > MIN_HALFFLOAT_EXP_N) { |
| i <<= 1; |
| n += 1; |
| } |
| if (uval < i) |
| n -= 1; |
| } else if (uval < i) { |
| while (uval < i && n < MAX_HALFFLOAT_EXP_N) { |
| i >>= 1; |
| n -= 1; |
| } |
| } |
| |
| return n; |
| } |
| |
| static int64_t exp2_int(int64_t n) |
| { |
| int p = n - 1; |
| |
| if (p > 0) |
| return (2 * MICRO_UNIT) << p; |
| else |
| return (2 * MICRO_UNIT) >> abs(p); |
| } |
| |
| static u16 float_encode(int64_t uval) |
| { |
| int sign = 0, n, exp, mantissa; |
| u16 half = 0; |
| |
| if (uval < 0) { |
| sign = 1; |
| uval = abs(uval); |
| } |
| n = log2_floor(uval); |
| exp = n + 15; |
| mantissa = div_s64(div_s64((uval - exp2_int(n)) * exp2_int(10 - n), |
| MICRO_UNIT) + MICRO_UNIT / 2, MICRO_UNIT); |
| |
| half = (mantissa & MANTISSA_MASK) | ((sign << 10) & SIGN) |
| | ((exp << 11) & EXPONENT_MASK); |
| |
| if (fg_debug_mask & FG_STATUS) |
| pr_info("uval = %lld, m = 0x%02x, sign = 0x%02x, exp = 0x%02x, half = 0x%04x\n", |
| uval, mantissa, sign, exp, half); |
| return half; |
| } |
| |
| #define BATT_IDED BIT(3) |
| static int fg_is_batt_id_valid(struct fg_chip *chip) |
| { |
| u8 fg_batt_sts; |
| int rc; |
| |
| 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 rc; |
| } |
| |
| if (fg_debug_mask & FG_IRQS) |
| pr_info("fg batt sts 0x%x\n", fg_batt_sts); |
| |
| return (fg_batt_sts & BATT_IDED) ? 1 : 0; |
| } |
| |
| static int64_t twos_compliment_extend(int64_t val, int nbytes) |
| { |
| int i; |
| int64_t mask; |
| |
| mask = 0x80LL << ((nbytes - 1) * 8); |
| if (val & mask) { |
| for (i = 8; i > nbytes; i--) { |
| mask = 0xFFLL << ((i - 1) * 8); |
| val |= mask; |
| } |
| } |
| |
| return val; |
| } |
| |
| #define LSB_16B_NUMRTR 152587 |
| #define LSB_16B_DENMTR 1000 |
| #define LSB_8B 9800 |
| #define TEMP_LSB_16B 625 |
| #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) |
| { |
| int i, j, rc = 0; |
| u8 reg[4]; |
| int64_t temp; |
| int battid_valid = fg_is_batt_id_valid(chip); |
| union power_supply_propval prop = {0, }; |
| |
| fg_stay_awake(&chip->update_sram_wakeup_source); |
| fg_mem_lock(chip); |
| for (i = 1; i < FG_DATA_MAX; i++) { |
| if (chip->profile_loaded && i >= FG_DATA_BATT_ID) |
| continue; |
| rc = fg_mem_read(chip, reg, fg_data[i].address, |
| fg_data[i].len, fg_data[i].offset, 0); |
| if (rc) { |
| pr_err("Failed to update sram data\n"); |
| break; |
| } |
| |
| temp = 0; |
| for (j = 0; j < fg_data[i].len; j++) |
| temp |= reg[j] << (8 * j); |
| |
| switch (i) { |
| case FG_DATA_OCV: |
| case FG_DATA_VOLTAGE: |
| case FG_DATA_CPRED_VOLTAGE: |
| fg_data[i].value = div_u64( |
| (u64)(u16)temp * LSB_16B_NUMRTR, |
| LSB_16B_DENMTR); |
| break; |
| case FG_DATA_CURRENT: |
| temp = twos_compliment_extend(temp, fg_data[i].len); |
| fg_data[i].value = div_s64( |
| (s64)temp * LSB_16B_NUMRTR, |
| LSB_16B_DENMTR); |
| break; |
| case FG_DATA_BATT_ESR: |
| fg_data[i].value = float_decode((u16) temp); |
| break; |
| case FG_DATA_BATT_ESR_COUNT: |
| fg_data[i].value = (u16)temp; |
| break; |
| case FG_DATA_BATT_ID: |
| if (battid_valid) |
| fg_data[i].value = reg[0] * LSB_8B; |
| break; |
| case FG_DATA_BATT_ID_INFO: |
| if (battid_valid) |
| fg_data[i].value = reg[0]; |
| break; |
| case FG_DATA_BATT_SOC: |
| fg_data[i].value = div64_s64((temp * 10000), |
| FULL_PERCENT_3B); |
| break; |
| case FG_DATA_CC_CHARGE: |
| temp = twos_compliment_extend(temp, fg_data[i].len); |
| fg_data[i].value = div64_s64( |
| temp * (int64_t)chip->nom_cap_uah, |
| FULL_PERCENT_28BIT); |
| break; |
| case FG_DATA_VINT_ERR: |
| temp = twos_compliment_extend(temp, fg_data[i].len); |
| fg_data[i].value = div64_s64(temp * chip->nom_cap_uah, |
| FULL_PERCENT_3B); |
| break; |
| }; |
| |
| if (fg_debug_mask & FG_MEM_DEBUG_READS) |
| pr_info("%d %lld %d\n", i, temp, fg_data[i].value); |
| } |
| fg_mem_release(chip); |
| |
| if (!rc) |
| get_current_time(&chip->last_sram_update_time); |
| |
| if (battid_valid) { |
| complete_all(&chip->batt_id_avail); |
| *resched_ms = fg_sram_update_period_ms; |
| } else { |
| *resched_ms = SRAM_PERIOD_NO_ID_UPDATE_MS; |
| } |
| /* Update compass compensation when charge related parameters changes */ |
| if (chip->batt_psy) |
| chip->batt_psy->set_property(chip->batt_psy, |
| POWER_SUPPLY_PROP_COMPASS_COMPENSATION, &prop); |
| fg_relax(&chip->update_sram_wakeup_source); |
| } |
| |
| #define SRAM_TIMEOUT_MS 3000 |
| 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, ret; |
| bool tried_again = false; |
| |
| wait: |
| /* Wait for MEMIF access revoked */ |
| ret = wait_for_completion_interruptible_timeout( |
| &chip->sram_access_revoked, |
| msecs_to_jiffies(SRAM_TIMEOUT_MS)); |
| |
| /* If we were interrupted wait again one more time. */ |
| if (ret == -ERESTARTSYS && !tried_again) { |
| tried_again = true; |
| goto wait; |
| } else if (ret <= 0) { |
| pr_err("transaction timed out ret=%d\n", ret); |
| goto out; |
| } |
| update_sram_data(chip, &resched_ms); |
| |
| out: |
| schedule_delayed_work( |
| &chip->update_sram_data, |
| msecs_to_jiffies(resched_ms)); |
| } |
| |
| #define BATT_TEMP_OFFSET 3 |
| #define BATT_TEMP_CNTRL_MASK 0x17 |
| #define DISABLE_THERM_BIT BIT(0) |
| #define TEMP_SENSE_ALWAYS_BIT BIT(1) |
| #define TEMP_SENSE_CHARGE_BIT BIT(2) |
| #define FORCE_RBIAS_ON_BIT BIT(4) |
| #define BATT_TEMP_OFF DISABLE_THERM_BIT |
| #define BATT_TEMP_ON (FORCE_RBIAS_ON_BIT | TEMP_SENSE_ALWAYS_BIT | \ |
| TEMP_SENSE_CHARGE_BIT) |
| #define TEMP_PERIOD_UPDATE_MS 10000 |
| #define TEMP_PERIOD_TIMEOUT_MS 3000 |
| static void update_temp_data(struct work_struct *work) |
| { |
| s16 temp; |
| u8 reg[2]; |
| bool tried_again = false; |
| int rc, ret, timeout = TEMP_PERIOD_TIMEOUT_MS; |
| struct fg_chip *chip = container_of(work, |
| struct fg_chip, |
| update_temp_work.work); |
| |
| fg_stay_awake(&chip->update_temp_wakeup_source); |
| if (chip->sw_rbias_ctrl) { |
| rc = fg_mem_masked_write(chip, EXTERNAL_SENSE_SELECT, |
| BATT_TEMP_CNTRL_MASK, |
| BATT_TEMP_ON, |
| BATT_TEMP_OFFSET); |
| if (rc) { |
| pr_err("failed to write BATT_TEMP_ON rc=%d\n", rc); |
| goto out; |
| } |
| |
| wait: |
| /* Wait for MEMIF access revoked */ |
| ret = wait_for_completion_interruptible_timeout( |
| &chip->sram_access_revoked, |
| msecs_to_jiffies(timeout)); |
| |
| /* If we were interrupted wait again one more time. */ |
| if (ret == -ERESTARTSYS && !tried_again) { |
| tried_again = true; |
| goto wait; |
| } else if (ret <= 0) { |
| rc = -ETIMEDOUT; |
| pr_err("transaction timed out ret=%d\n", ret); |
| goto out; |
| } |
| } |
| |
| /* Read FG_DATA_BATT_TEMP now */ |
| rc = fg_mem_read(chip, reg, fg_data[0].address, |
| fg_data[0].len, fg_data[0].offset, |
| chip->sw_rbias_ctrl ? 1 : 0); |
| if (rc) { |
| pr_err("Failed to update temp data\n"); |
| goto out; |
| } |
| |
| temp = reg[0] | (reg[1] << 8); |
| fg_data[0].value = (temp * TEMP_LSB_16B / 1000) |
| - DECIKELVIN; |
| |
| 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); |
| |
| out: |
| if (chip->sw_rbias_ctrl) { |
| rc = fg_mem_masked_write(chip, EXTERNAL_SENSE_SELECT, |
| BATT_TEMP_CNTRL_MASK, |
| BATT_TEMP_OFF, |
| BATT_TEMP_OFFSET); |
| if (rc) |
| pr_err("failed to write BATT_TEMP_OFF rc=%d\n", rc); |
| } |
| schedule_delayed_work( |
| &chip->update_temp_work, |
| msecs_to_jiffies(TEMP_PERIOD_UPDATE_MS)); |
| fg_relax(&chip->update_temp_wakeup_source); |
| } |
| |
| static void update_jeita_setting(struct work_struct *work) |
| { |
| struct fg_chip *chip = container_of(work, |
| struct fg_chip, |
| update_jeita_setting.work); |
| u8 reg[4]; |
| int i, rc; |
| |
| for (i = 0; i < 4; i++) |
| reg[i] = (settings[FG_MEM_SOFT_COLD + i].value / 10) + 30; |
| |
| rc = fg_mem_write(chip, reg, settings[FG_MEM_SOFT_COLD].address, |
| 4, settings[FG_MEM_SOFT_COLD].offset, 0); |
| if (rc) |
| pr_err("failed to update JEITA setting rc=%d\n", rc); |
| } |
| |
| static int fg_set_resume_soc(struct fg_chip *chip, u8 threshold) |
| { |
| u16 address; |
| int offset, rc; |
| |
| address = settings[FG_MEM_RESUME_SOC].address; |
| offset = settings[FG_MEM_RESUME_SOC].offset; |
| |
| rc = fg_mem_masked_write(chip, address, 0xFF, threshold, offset); |
| |
| if (rc) |
| pr_err("write failed rc=%d\n", rc); |
| else |
| pr_debug("setting resume-soc to %x\n", 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) |
| { |
| int rc = 0, i, address; |
| u8 data[2]; |
| |
| fg_mem_lock(chip); |
| for (i = 0; i < BUCKET_COUNT; i++) { |
| address = BATT_CYCLE_NUMBER_REG + i * 2; |
| rc = fg_mem_read(chip, (u8 *)&data, address, 2, |
| BATT_CYCLE_OFFSET, 0); |
| if (rc) |
| pr_err("Failed to read BATT_CYCLE_NUMBER[%d] rc: %d\n", |
| i, rc); |
| else |
| chip->cyc_ctr.count[i] = data[0] | data[1] << 8; |
| } |
| fg_mem_release(chip); |
| } |
| |
| static void clear_cycle_counter(struct fg_chip *chip) |
| { |
| int rc = 0, len, i; |
| |
| if (!chip->cyc_ctr.en) |
| return; |
| |
| len = sizeof(chip->cyc_ctr.count); |
| memset(chip->cyc_ctr.count, 0, len); |
| for (i = 0; i < BUCKET_COUNT; i++) { |
| chip->cyc_ctr.started[i] = false; |
| chip->cyc_ctr.last_soc[i] = 0; |
| } |
| rc = fg_mem_write(chip, (u8 *)&chip->cyc_ctr.count, |
| BATT_CYCLE_NUMBER_REG, len, |
| BATT_CYCLE_OFFSET, 0); |
| if (rc) |
| pr_err("failed to write BATT_CYCLE_NUMBER rc=%d\n", rc); |
| } |
| |
| static int fg_inc_store_cycle_ctr(struct fg_chip *chip, int bucket) |
| { |
| int rc = 0, address; |
| u16 cyc_count; |
| u8 data[2]; |
| |
| if (bucket < 0 || (bucket > BUCKET_COUNT - 1)) |
| return 0; |
| |
| cyc_count = chip->cyc_ctr.count[bucket]; |
| cyc_count++; |
| data[0] = cyc_count & 0xFF; |
| data[1] = cyc_count >> 8; |
| |
| address = BATT_CYCLE_NUMBER_REG + bucket * 2; |
| |
| rc = fg_mem_write(chip, data, address, 2, BATT_CYCLE_OFFSET, 0); |
| if (rc) |
| pr_err("failed to write BATT_CYCLE_NUMBER[%d] rc=%d\n", |
| bucket, rc); |
| else |
| chip->cyc_ctr.count[bucket] = cyc_count; |
| return rc; |
| } |
| |
| static void update_cycle_count(struct work_struct *work) |
| { |
| int rc = 0, bucket, i; |
| u8 reg[3], batt_soc; |
| struct fg_chip *chip = container_of(work, |
| struct fg_chip, |
| cycle_count_work); |
| |
| mutex_lock(&chip->cyc_ctr.lock); |
| rc = fg_mem_read(chip, reg, BATTERY_SOC_REG, 3, |
| BATTERY_SOC_OFFSET, 0); |
| if (rc) { |
| pr_err("Failed to read battery soc rc: %d\n", rc); |
| goto out; |
| } |
| batt_soc = reg[2]; |
| |
| if (chip->status == POWER_SUPPLY_STATUS_CHARGING) { |
| /* Find out which bucket the SOC falls in */ |
| bucket = batt_soc / BUCKET_SOC_PCT; |
| |
| if (fg_debug_mask & FG_STATUS) |
| pr_info("batt_soc: %x bucket: %d\n", reg[2], bucket); |
| |
| /* |
| * If we've started counting for the previous bucket, |
| * then store the counter for that bucket if the |
| * counter for current bucket is getting started. |
| */ |
| if (bucket > 0 && chip->cyc_ctr.started[bucket - 1] && |
| !chip->cyc_ctr.started[bucket]) { |
| rc = fg_inc_store_cycle_ctr(chip, bucket - 1); |
| if (rc) { |
| pr_err("Error in storing cycle_ctr rc: %d\n", |
| rc); |
| goto out; |
| } else { |
| chip->cyc_ctr.started[bucket - 1] = false; |
| chip->cyc_ctr.last_soc[bucket - 1] = 0; |
| } |
| } |
| if (!chip->cyc_ctr.started[bucket]) { |
| chip->cyc_ctr.started[bucket] = true; |
| chip->cyc_ctr.last_soc[bucket] = batt_soc; |
| } |
| } else { |
| for (i = 0; i < BUCKET_COUNT; i++) { |
| if (chip->cyc_ctr.started[i] && |
| batt_soc > chip->cyc_ctr.last_soc[i]) { |
| rc = fg_inc_store_cycle_ctr(chip, i); |
| if (rc) |
| pr_err("Error in storing cycle_ctr rc: %d\n", |
| rc); |
| chip->cyc_ctr.last_soc[i] = 0; |
| } |
| chip->cyc_ctr.started[i] = false; |
| } |
| } |
| out: |
| mutex_unlock(&chip->cyc_ctr.lock); |
| } |
| |
| static int fg_get_cycle_count(struct fg_chip *chip) |
| { |
| int count; |
| |
| if (!chip->cyc_ctr.en) |
| return 0; |
| |
| if ((chip->cyc_ctr.id <= 0) || (chip->cyc_ctr.id > BUCKET_COUNT)) |
| return -EINVAL; |
| |
| mutex_lock(&chip->cyc_ctr.lock); |
| count = chip->cyc_ctr.count[chip->cyc_ctr.id - 1]; |
| mutex_unlock(&chip->cyc_ctr.lock); |
| return count; |
| } |
| |
| static void half_float_to_buffer(int64_t uval, u8 *buffer) |
| { |
| u16 raw; |
| |
| raw = float_encode(uval); |
| buffer[0] = (u8)(raw & 0xFF); |
| buffer[1] = (u8)((raw >> 8) & 0xFF); |
| } |
| |
| static int64_t half_float(u8 *buffer) |
| { |
| u16 val; |
| |
| val = buffer[1] << 8 | buffer[0]; |
| return float_decode(val); |
| } |
| |
| static int voltage_2b(u8 *buffer) |
| { |
| u16 val; |
| |
| val = buffer[1] << 8 | buffer[0]; |
| /* the range of voltage 2b is [-5V, 5V], so it will fit in an int */ |
| return (int)div_u64(((u64)val) * LSB_16B_NUMRTR, LSB_16B_DENMTR); |
| } |
| |
| static int bcap_uah_2b(u8 *buffer) |
| { |
| u16 val; |
| |
| val = buffer[1] << 8 | buffer[0]; |
| return ((int)val) * 1000; |
| } |
| |
| static int lookup_ocv_for_soc(struct fg_chip *chip, int soc) |
| { |
| int64_t *coeffs; |
| |
| if (soc > chip->ocv_junction_p1p2 * 10) |
| coeffs = chip->ocv_coeffs; |
| else if (soc > chip->ocv_junction_p2p3 * 10) |
| coeffs = chip->ocv_coeffs + 4; |
| else |
| coeffs = chip->ocv_coeffs + 8; |
| /* the range of ocv will fit in a 32 bit int */ |
| return (int)(coeffs[0] |
| + div_s64(coeffs[1] * soc, 1000LL) |
| + div_s64(coeffs[2] * soc * soc, 1000000LL) |
| + div_s64(coeffs[3] * soc * soc * soc, 1000000000LL)); |
| } |
| |
| static int lookup_soc_for_ocv(struct fg_chip *chip, int ocv) |
| { |
| int64_t val; |
| int soc = -EINVAL; |
| /* |
| * binary search variables representing the valid start and end |
| * percentages to search |
| */ |
| int start = 0, end = 1000, mid; |
| |
| if (fg_debug_mask & FG_AGING) |
| pr_info("target_ocv = %d\n", ocv); |
| /* do a binary search for the closest soc to match the ocv */ |
| while (end - start > 1) { |
| mid = (start + end) / 2; |
| val = lookup_ocv_for_soc(chip, mid); |
| if (fg_debug_mask & FG_AGING) |
| pr_info("start = %d, mid = %d, end = %d, ocv = %lld\n", |
| start, mid, end, val); |
| if (ocv < val) { |
| end = mid; |
| } else if (ocv > val) { |
| start = mid; |
| } else { |
| soc = mid; |
| break; |
| } |
| } |
| /* |
| * if the exact soc was not found and there are two or less values |
| * remaining, just compare them and see which one is closest to the ocv |
| */ |
| if (soc == -EINVAL) { |
| if (abs(ocv - lookup_ocv_for_soc(chip, start)) |
| > abs(ocv - lookup_ocv_for_soc(chip, end))) |
| soc = end; |
| else |
| soc = start; |
| } |
| if (fg_debug_mask & FG_AGING) |
| pr_info("closest = %d, target_ocv = %d, ocv_found = %d\n", |
| soc, ocv, lookup_ocv_for_soc(chip, soc)); |
| return soc; |
| } |
| |
| #define ESR_ACTUAL_REG 0x554 |
| #define BATTERY_ESR_REG 0x4F4 |
| #define TEMP_RS_TO_RSLOW_REG 0x514 |
| static int estimate_battery_age(struct fg_chip *chip, int *actual_capacity) |
| { |
| int64_t ocv_cutoff_new, ocv_cutoff_aged, temp_rs_to_rslow; |
| int64_t esr_actual, battery_esr, val; |
| int soc_cutoff_aged, soc_cutoff_new, rc; |
| int battery_soc, unusable_soc, batt_temp; |
| u8 buffer[3]; |
| |
| if (chip->batt_aging_mode != FG_AGING_ESR) |
| return 0; |
| |
| if (chip->nom_cap_uah == 0) { |
| if (fg_debug_mask & FG_AGING) |
| pr_info("ocv coefficients not loaded, aborting\n"); |
| return 0; |
| } |
| fg_mem_lock(chip); |
| |
| batt_temp = get_sram_prop_now(chip, FG_DATA_BATT_TEMP); |
| if (batt_temp < 150 || batt_temp > 400) { |
| if (fg_debug_mask & FG_AGING) |
| pr_info("Battery temp (%d) out of range, aborting\n", |
| (int)batt_temp); |
| rc = 0; |
| goto done; |
| } |
| |
| battery_soc = get_battery_soc_raw(chip) * 100 / FULL_PERCENT_3B; |
| if (rc) { |
| goto error_done; |
| } else if (battery_soc < 25 || battery_soc > 75) { |
| if (fg_debug_mask & FG_AGING) |
| pr_info("Battery SoC (%d) out of range, aborting\n", |
| (int)battery_soc); |
| rc = 0; |
| goto done; |
| } |
| |
| 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); |
| battery_esr = half_float(buffer); |
| |
| if (rc) { |
| goto error_done; |
| } else if (esr_actual < battery_esr) { |
| if (fg_debug_mask & FG_AGING) |
| pr_info("Batt ESR lower than ESR actual, aborting\n"); |
| rc = 0; |
| goto done; |
| } |
| rc = fg_mem_read(chip, buffer, TEMP_RS_TO_RSLOW_REG, 2, 0, 0); |
| temp_rs_to_rslow = half_float(buffer); |
| |
| if (rc) |
| goto error_done; |
| |
| fg_mem_release(chip); |
| |
| if (fg_debug_mask & FG_AGING) { |
| pr_info("batt_soc = %d, cutoff_voltage = %lld, eval current = %d\n", |
| battery_soc, chip->cutoff_voltage, |
| chip->evaluation_current); |
| pr_info("temp_rs_to_rslow = %lld, batt_esr = %lld, esr_actual = %lld\n", |
| temp_rs_to_rslow, battery_esr, esr_actual); |
| } |
| |
| /* calculate soc_cutoff_new */ |
| val = (1000000LL + temp_rs_to_rslow) * battery_esr; |
| do_div(val, 1000000); |
| ocv_cutoff_new = div64_s64(chip->evaluation_current * val, 1000) |
| + chip->cutoff_voltage; |
| |
| /* calculate soc_cutoff_aged */ |
| val = (1000000LL + temp_rs_to_rslow) * esr_actual; |
| do_div(val, 1000000); |
| ocv_cutoff_aged = div64_s64(chip->evaluation_current * val, 1000) |
| + chip->cutoff_voltage; |
| |
| if (fg_debug_mask & FG_AGING) |
| pr_info("ocv_cutoff_new = %lld, ocv_cutoff_aged = %lld\n", |
| ocv_cutoff_new, ocv_cutoff_aged); |
| |
| soc_cutoff_new = lookup_soc_for_ocv(chip, ocv_cutoff_new); |
| soc_cutoff_aged = lookup_soc_for_ocv(chip, ocv_cutoff_aged); |
| |
| if (fg_debug_mask & FG_AGING) |
| pr_info("aged soc = %d, new soc = %d\n", |
| soc_cutoff_aged, soc_cutoff_new); |
| unusable_soc = soc_cutoff_aged - soc_cutoff_new; |
| |
| *actual_capacity = div64_s64(((int64_t)chip->nom_cap_uah) |
| * (1000 - unusable_soc), 1000); |
| if (fg_debug_mask & FG_AGING) |
| pr_info("nom cap = %d, actual cap = %d\n", |
| chip->nom_cap_uah, *actual_capacity); |
| |
| return rc; |
| |
| error_done: |
| pr_err("some register reads failed: %d\n", rc); |
| done: |
| fg_mem_release(chip); |
| return rc; |
| } |
| |
| static void battery_age_work(struct work_struct *work) |
| { |
| struct fg_chip *chip = container_of(work, |
| struct fg_chip, |
| battery_age_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_PROFILE_STATUS, |
| POWER_SUPPLY_PROP_CYCLE_COUNT_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 = container_of(psy, struct fg_chip, bms_psy); |
| bool vbatt_low_sts; |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_BATTERY_TYPE: |
| 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_PROFILE_STATUS: |
| val->intval = chip->profile_loaded; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int correction_times[] = { |
| 1470, |
| 2940, |
| 4410, |
| 5880, |
| 7350, |
| 8820, |
| 10290, |
| 11760, |
| 13230, |
| 14700, |
| 16170, |
| 17640, |
| 19110, |
| 20580, |
| 22050, |
| 23520, |
| 24990, |
| 26460, |
| 27930, |
| 29400, |
| 30870, |
| 32340, |
| 33810, |
| 35280, |
| 36750, |
| 38220, |
| 39690, |
| 41160, |
| 42630, |
| 44100, |
| 45570, |
| 47040, |
| }; |
| |
| static int correction_factors[] = { |
| 1000000, |
| 1007874, |
| 1015789, |
| 1023745, |
| 1031742, |
| 1039780, |
| 1047859, |
| 1055979, |
| 1064140, |
| 1072342, |
| 1080584, |
| 1088868, |
| 1097193, |
| 1105558, |
| 1113964, |
| 1122411, |
| 1130899, |
| 1139427, |
| 1147996, |
| 1156606, |
| 1165256, |
| 1173947, |
| 1182678, |
| 1191450, |
| 1200263, |
| 1209115, |
| 1218008, |
| 1226942, |
| 1235915, |
| 1244929, |
| 1253983, |
| 1263076, |
| }; |
| |
| #define FG_CONVERSION_FACTOR (64198531LL) |
| static int iavg_3b_to_uah(u8 *buffer, int delta_ms) |
| { |
| int64_t val, i_filtered; |
| int i, correction_factor; |
| |
| for (i = 0; i < ARRAY_SIZE(correction_times); i++) { |
| if (correction_times[i] > delta_ms) |
| break; |
| } |
| if (i >= ARRAY_SIZE(correction_times)) { |
| if (fg_debug_mask & FG_STATUS) |
| pr_info("fuel gauge took more than 32 cycles\n"); |
| i = ARRAY_SIZE(correction_times) - 1; |
| } |
| correction_factor = correction_factors[i]; |
| if (fg_debug_mask & FG_STATUS) |
| pr_info("delta_ms = %d, cycles = %d, correction = %d\n", |
| delta_ms, i, correction_factor); |
| val = buffer[2] << 16 | buffer[1] << 8 | buffer[0]; |
| /* convert val from signed 24b to signed 64b */ |
| i_filtered = (val << 40) >> 40; |
| val = i_filtered * correction_factor; |
| val = div64_s64(val + FG_CONVERSION_FACTOR / 2, FG_CONVERSION_FACTOR); |
| if (fg_debug_mask & FG_STATUS) |
| pr_info("i_filtered = 0x%llx/%lld, cc_uah = %lld\n", |
| i_filtered, i_filtered, val); |
| |
| return val; |
| } |
| |
| static bool fg_is_temperature_ok_for_learning(struct fg_chip *chip) |
| { |
| int batt_temp = get_sram_prop_now(chip, FG_DATA_BATT_TEMP); |
| |
| if (batt_temp > chip->learning_data.max_temp |
| || batt_temp < chip->learning_data.min_temp) { |
| if (fg_debug_mask & FG_AGING) |
| pr_info("temp (%d) out of range [%d, %d], aborting\n", |
| batt_temp, |
| chip->learning_data.min_temp, |
| chip->learning_data.max_temp); |
| return false; |
| } |
| return true; |
| } |
| |
| static void fg_cap_learning_stop(struct fg_chip *chip) |
| { |
| chip->learning_data.cc_uah = 0; |
| chip->learning_data.active = false; |
| } |
| |
| #define I_FILTERED_REG 0x584 |
| static void fg_cap_learning_work(struct work_struct *work) |
| { |
| struct fg_chip *chip = container_of(work, |
| struct fg_chip, |
| fg_cap_learning_work); |
| u8 i_filtered[3], data[3]; |
| int rc, cc_uah, delta_ms; |
| ktime_t now_kt, delta_kt; |
| |
| mutex_lock(&chip->learning_data.learning_lock); |
| if (!chip->learning_data.active) |
| goto fail; |
| if (!fg_is_temperature_ok_for_learning(chip)) { |
| fg_cap_learning_stop(chip); |
| goto fail; |
| } |
| fg_mem_lock(chip); |
| |
| rc = fg_mem_read(chip, i_filtered, I_FILTERED_REG, 3, 0, 0); |
| if (rc) { |
| pr_err("Failed to read i_filtered: %d\n", rc); |
| fg_mem_release(chip); |
| goto fail; |
| } |
| memset(data, 0, 3); |
| rc = fg_mem_write(chip, data, I_FILTERED_REG, 3, 0, 0); |
| if (rc) { |
| pr_err("Failed to clear i_filtered: %d\n", rc); |
| fg_mem_release(chip); |
| goto fail; |
| } |
| fg_mem_release(chip); |
| |
| now_kt = ktime_get_boottime(); |
| delta_kt = ktime_sub(now_kt, chip->learning_data.time_stamp); |
| chip->learning_data.time_stamp = now_kt; |
| |
| delta_ms = (int)div64_s64(ktime_to_ns(delta_kt), 1000000); |
| |
| cc_uah = iavg_3b_to_uah(i_filtered, delta_ms); |
| chip->learning_data.cc_uah -= cc_uah; |
| if (fg_debug_mask & FG_AGING) |
| pr_info("total_cc_uah = %lld\n", chip->learning_data.cc_uah); |
| |
| fail: |
| mutex_unlock(&chip->learning_data.learning_lock); |
| return; |
| |
| } |
| |
| #define FG_CAP_LEARNING_INTERVAL_NS 30000000000 |
| static enum alarmtimer_restart fg_cap_learning_alarm_cb(struct alarm *alarm, |
| ktime_t now) |
| { |
| struct fg_chip *chip = container_of(alarm, struct fg_chip, |
| fg_cap_learning_alarm); |
| |
| if (chip->learning_data.active) { |
| if (fg_debug_mask & FG_AGING) |
| pr_info("alarm fired\n"); |
| schedule_work(&chip->fg_cap_learning_work); |
| alarm_forward_now(alarm, |
| ns_to_ktime(FG_CAP_LEARNING_INTERVAL_NS)); |
| return ALARMTIMER_RESTART; |
| } |
| if (fg_debug_mask & FG_AGING) |
| pr_info("alarm misfired\n"); |
| return ALARMTIMER_NORESTART; |
| } |
| |
| #define FG_AGING_STORAGE_REG 0x5E4 |
| #define ACTUAL_CAPACITY_REG 0x578 |
| #define MAH_TO_SOC_CONV_REG 0x4A0 |
| #define CC_SOC_COEFF_OFFSET 0 |
| #define ACTUAL_CAPACITY_OFFSET 2 |
| #define MAH_TO_SOC_CONV_CS_OFFSET 0 |
| static void fg_cap_learning_load_data(struct fg_chip *chip) |
| { |
| int16_t cc_mah; |
| int64_t old_cap = chip->learning_data.learned_cc_uah; |
| int rc; |
| |
| rc = fg_mem_read(chip, (u8 *)&cc_mah, FG_AGING_STORAGE_REG, 2, 0, 0); |
| if (rc) { |
| pr_err("Failed to load aged capacity: %d\n", rc); |
| } else { |
| chip->learning_data.learned_cc_uah = cc_mah * 1000; |
| if (fg_debug_mask & FG_AGING) |
| pr_info("learned capacity %lld-> %lld/%x uah\n", |
| old_cap, |
| chip->learning_data.learned_cc_uah, |
| cc_mah); |
| } |
| } |
| |
| static void fg_cap_learning_save_data(struct fg_chip *chip) |
| { |
| int16_t cc_mah; |
| int64_t cc_to_soc_coeff, mah_to_soc; |
| int rc; |
| u8 data[2]; |
| |
| cc_mah = div64_s64(chip->learning_data.learned_cc_uah, 1000); |
| |
| rc = fg_mem_write(chip, (u8 *)&cc_mah, FG_AGING_STORAGE_REG, 2, 0, 0); |
| if (rc) |
| pr_err("Failed to store aged capacity: %d\n", rc); |
| else if (fg_debug_mask & FG_AGING) |
| pr_info("learned capacity %lld uah (%d/0x%x uah) saved to sram\n", |
| chip->learning_data.learned_cc_uah, |
| cc_mah, cc_mah); |
| |
| if (chip->learning_data.feedback_on) { |
| rc = fg_mem_write(chip, (u8 *)&cc_mah, ACTUAL_CAPACITY_REG, 2, |
| ACTUAL_CAPACITY_OFFSET, 0); |
| if (rc) |
| pr_err("Failed to store actual capacity: %d\n", rc); |
| |
| rc = fg_mem_read(chip, (u8 *)&data, MAH_TO_SOC_CONV_REG, 2, |
| MAH_TO_SOC_CONV_CS_OFFSET, 0); |
| if (rc) { |
| pr_err("Failed to read mah_to_soc_conv_cs: %d\n", rc); |
| } else { |
| mah_to_soc = data[1] << 8 | data[0]; |
| mah_to_soc *= MICRO_UNIT; |
| cc_to_soc_coeff = div64_s64(mah_to_soc, cc_mah); |
| half_float_to_buffer(cc_to_soc_coeff, data); |
| rc = fg_mem_write(chip, (u8 *)data, |
| ACTUAL_CAPACITY_REG, 2, |
| CC_SOC_COEFF_OFFSET, 0); |
| if (rc) |
| pr_err("Failed to write cc_soc_coeff_offset: %d\n", |
| rc); |
| else if (fg_debug_mask & FG_AGING) |
| pr_info("new cc_soc_coeff %lld [%x %x] saved to sram\n", |
| cc_to_soc_coeff, data[0], data[1]); |
| } |
| } |
| } |
| |
| static void fg_cap_learning_post_process(struct fg_chip *chip) |
| { |
| int64_t max_inc_val, min_dec_val, old_cap; |
| |
| max_inc_val = chip->learning_data.learned_cc_uah |
| * (1000 + chip->learning_data.max_increment); |
| do_div(max_inc_val, 1000); |
| |
| min_dec_val = chip->learning_data.learned_cc_uah |
| * (1000 - chip->learning_data.max_decrement); |
| do_div(min_dec_val, 1000); |
| |
| old_cap = chip->learning_data.learned_cc_uah; |
| if (chip->learning_data.cc_uah > max_inc_val) |
| chip->learning_data.learned_cc_uah = max_inc_val; |
| else if (chip->learning_data.cc_uah < min_dec_val) |
| chip->learning_data.learned_cc_uah = min_dec_val; |
| else |
| chip->learning_data.learned_cc_uah = |
| chip->learning_data.cc_uah; |
| |
| fg_cap_learning_save_data(chip); |
| if (fg_debug_mask & FG_AGING) |
| pr_info("final cc_uah = %lld, learned capacity %lld -> %lld uah\n", |
| chip->learning_data.cc_uah, |
| old_cap, chip->learning_data.learned_cc_uah); |
| } |
| |
| static int get_vbat_est_diff(struct fg_chip *chip) |
| { |
| return abs(fg_data[FG_DATA_VOLTAGE].value |
| - fg_data[FG_DATA_CPRED_VOLTAGE].value); |
| } |
| |
| #define CBITS_INPUT_FILTER_REG 0x4B4 |
| #define IBATTF_TAU_MASK 0x38 |
| #define IBATTF_TAU_99_S 0x30 |
| static int fg_cap_learning_check(struct fg_chip *chip) |
| { |
| u8 data[3]; |
| int rc = 0, battery_soc; |
| int vbat_est_diff, vbat_est_thr_uv; |
| |
| mutex_lock(&chip->learning_data.learning_lock); |
| if (chip->status == POWER_SUPPLY_STATUS_CHARGING |
| && !chip->learning_data.active |
| && chip->batt_aging_mode == FG_AGING_CC) { |
| if (chip->learning_data.learned_cc_uah == 0) { |
| if (fg_debug_mask & FG_AGING) |
| pr_info("no capacity, aborting\n"); |
| goto fail; |
| } |
| |
| if (!fg_is_temperature_ok_for_learning(chip)) |
| goto fail; |
| |
| fg_mem_lock(chip); |
| if (!chip->learning_data.feedback_on) { |
| vbat_est_diff = get_vbat_est_diff(chip); |
| vbat_est_thr_uv = chip->learning_data.vbat_est_thr_uv; |
| if (vbat_est_diff >= vbat_est_thr_uv && |
| vbat_est_thr_uv > 0) { |
| if (fg_debug_mask & FG_AGING) |
| pr_info("vbat_est_diff (%d) < threshold (%d)\n", |
| vbat_est_diff, vbat_est_thr_uv); |
| fg_mem_release(chip); |
| fg_cap_learning_stop(chip); |
| goto fail; |
| } |
| } |
| battery_soc = get_battery_soc_raw(chip); |
| if (fg_debug_mask & FG_AGING) |
| pr_info("checking battery soc (%d vs %d)\n", |
| battery_soc * 100 / FULL_PERCENT_3B, |
| chip->learning_data.max_start_soc); |
| /* check if the battery is low enough to start soc learning */ |
| 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", |
| battery_soc * 100 / FULL_PERCENT_3B, |
| chip->learning_data.max_start_soc); |
| fg_mem_release(chip); |
| fg_cap_learning_stop(chip); |
| goto fail; |
| } |
| |
| /* set the coulomb counter to a percentage of the capacity */ |
| chip->learning_data.cc_uah = div64_s64( |
| (chip->learning_data.learned_cc_uah * battery_soc), |
| FULL_PERCENT_3B); |
| |
| rc = fg_mem_masked_write(chip, CBITS_INPUT_FILTER_REG, |
| IBATTF_TAU_MASK, IBATTF_TAU_99_S, 0); |
| if (rc) { |
| pr_err("Failed to write IF IBAT Tau: %d\n", rc); |
| fg_mem_release(chip); |
| fg_cap_learning_stop(chip); |
| goto fail; |
| } |
| /* clear the i_filtered register */ |
| memset(data, 0, 3); |
| rc = fg_mem_write(chip, data, I_FILTERED_REG, 3, 0, 0); |
| if (rc) { |
| pr_err("Failed to clear i_filtered: %d\n", rc); |
| fg_mem_release(chip); |
| fg_cap_learning_stop(chip); |
| goto fail; |
| } |
| fg_mem_release(chip); |
| chip->learning_data.time_stamp = ktime_get_boottime(); |
| chip->learning_data.active = true; |
| |
| if (fg_debug_mask & FG_AGING) |
| pr_info("cap learning started, soc = %d cc_uah = %lld\n", |
| battery_soc * 100 / FULL_PERCENT_3B, |
| chip->learning_data.cc_uah); |
| rc = alarm_start_relative(&chip->fg_cap_learning_alarm, |
| ns_to_ktime(FG_CAP_LEARNING_INTERVAL_NS)); |
| if (rc) { |
| pr_err("Failed to start alarm: %d\n", rc); |
| fg_cap_learning_stop(chip); |
| goto fail; |
| } |
| } else if (chip->status != POWER_SUPPLY_STATUS_CHARGING |
| && chip->learning_data.active) { |
| if (fg_debug_mask & FG_AGING) |
| pr_info("capacity learning stopped\n"); |
| alarm_try_to_cancel(&chip->fg_cap_learning_alarm); |
| |
| if (chip->status == POWER_SUPPLY_STATUS_FULL) |
| fg_cap_learning_post_process(chip); |
| fg_cap_learning_stop(chip); |
| } |
| |
| fail: |
| mutex_unlock(&chip->learning_data.learning_lock); |
| return rc; |
| } |
| |
| static bool is_usb_present(struct fg_chip *chip) |
| { |
| union power_supply_propval prop = {0,}; |
| if (!chip->usb_psy) |
| chip->usb_psy = power_supply_get_by_name("usb"); |
| |
| if (chip->usb_psy) |
| chip->usb_psy->get_property(chip->usb_psy, |
| POWER_SUPPLY_PROP_PRESENT, &prop); |
| return prop.intval != 0; |
| } |
| |
| static bool is_dc_present(struct fg_chip *chip) |
| { |
| union power_supply_propval prop = {0,}; |
| if (!chip->dc_psy) |
| chip->dc_psy = power_supply_get_by_name("dc"); |
| |
| if (chip->dc_psy) |
| chip->dc_psy->get_property(chip->dc_psy, |
| POWER_SUPPLY_PROP_PRESENT, &prop); |
| return prop.intval != 0; |
| } |
| |
| static bool is_input_present(struct fg_chip *chip) |
| { |
| return is_usb_present(chip) || is_dc_present(chip); |
| } |
| |
| static void status_change_work(struct work_struct *work) |
| { |
| struct fg_chip *chip = container_of(work, |
| struct fg_chip, |
| status_change_work); |
| unsigned long current_time = 0; |
| int capacity = get_prop_capacity(chip); |
| |
| if (chip->status == POWER_SUPPLY_STATUS_FULL) { |
| if (capacity >= 99 && chip->hold_soc_while_full) { |
| if (fg_debug_mask & FG_STATUS) |
| pr_info("holding soc at 100\n"); |
| chip->charge_full = true; |
| } else if (fg_debug_mask & FG_STATUS) { |
| pr_info("terminated charging at %d/0x%02x\n", |
| capacity, get_monotonic_soc_raw(chip)); |
| } |
| } |
| if (chip->status == POWER_SUPPLY_STATUS_FULL || |
| chip->status == POWER_SUPPLY_STATUS_CHARGING) { |
| 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 (capacity == 100) |
| fg_configure_soc(chip); |
| } else if (chip->status == POWER_SUPPLY_STATUS_DISCHARGING) { |
| 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; |
| } |
| } |
| fg_cap_learning_check(chip); |
| schedule_work(&chip->update_esr_work); |
| if (chip->prev_status != chip->status && chip->last_sram_update_time) { |
| get_current_time(¤t_time); |
| /* |
| * When charging status changes, update SRAM parameters if it |
| * was not updated before 5 seconds from now. |
| */ |
| if (chip->last_sram_update_time + 5 < current_time) { |
| cancel_delayed_work(&chip->update_sram_data); |
| schedule_delayed_work(&chip->update_sram_data, |
| msecs_to_jiffies(0)); |
| } |
| if (chip->cyc_ctr.en) |
| schedule_work(&chip->cycle_count_work); |
| } |
| } |
| |
| static int fg_power_set_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| const union power_supply_propval *val) |
| { |
| struct fg_chip *chip = container_of(psy, struct fg_chip, bms_psy); |
| int rc = 0, unused; |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_COOL_TEMP: |
| rc = set_prop_jeita_temp(chip, FG_MEM_SOFT_COLD, val->intval); |
| break; |
| case POWER_SUPPLY_PROP_WARM_TEMP: |
| rc = set_prop_jeita_temp(chip, FG_MEM_SOFT_HOT, val->intval); |
| break; |
| case POWER_SUPPLY_PROP_UPDATE_NOW: |
| if (val->intval) |
| update_sram_data(chip, &unused); |
| break; |
| case POWER_SUPPLY_PROP_STATUS: |
| chip->prev_status = chip->status; |
| chip->status = val->intval; |
| schedule_work(&chip->status_change_work); |
| break; |
| case POWER_SUPPLY_PROP_HEALTH: |
| chip->health = val->intval; |
| if (chip->health == POWER_SUPPLY_HEALTH_GOOD) { |
| fg_stay_awake(&chip->resume_soc_wakeup_source); |
| schedule_work(&chip->set_resume_soc_work); |
| } |
| break; |
| case POWER_SUPPLY_PROP_CHARGE_DONE: |
| chip->charge_done = val->intval; |
| if (!chip->resume_soc_lowered) { |
| fg_stay_awake(&chip->resume_soc_wakeup_source); |
| schedule_work(&chip->set_resume_soc_work); |
| } |
| break; |
| case POWER_SUPPLY_PROP_CYCLE_COUNT_ID: |
| if ((val->intval > 0) && (val->intval <= BUCKET_COUNT)) { |
| chip->cyc_ctr.id = val->intval; |
| } else { |
| pr_err("rejecting invalid cycle_count_id = %d\n", |
| val->intval); |
| rc = -EINVAL; |
| } |
| break; |
| default: |
| return -EINVAL; |
| }; |
| |
| return rc; |
| }; |
| |
| static int fg_property_is_writeable(struct power_supply *psy, |
| enum power_supply_property psp) |
| { |
| switch (psp) { |
| case POWER_SUPPLY_PROP_COOL_TEMP: |
| case POWER_SUPPLY_PROP_WARM_TEMP: |
| case POWER_SUPPLY_PROP_CYCLE_COUNT_ID: |
| return 1; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| #define SRAM_DUMP_START 0x400 |
| #define SRAM_DUMP_LEN 0x200 |
| static void dump_sram(struct work_struct *work) |
| { |
| int i, rc; |
| u8 *buffer, rt_sts; |
| char str[16]; |
| struct fg_chip *chip = container_of(work, |
| struct fg_chip, |
| dump_sram); |
| |
| buffer = devm_kzalloc(chip->dev, SRAM_DUMP_LEN, GFP_KERNEL); |
| if (buffer == NULL) { |
| pr_err("Can't allocate buffer\n"); |
| return; |
| } |
| |
| rc = fg_read(chip, &rt_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); |
| else |
| pr_info("soc rt_sts: 0x%x\n", rt_sts); |
| |
| rc = fg_read(chip, &rt_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 |
| pr_info("batt rt_sts: 0x%x\n", rt_sts); |
| |
| rc = fg_read(chip, &rt_sts, INT_RT_STS(chip->mem_base), 1); |
| if (rc) |
| pr_err("spmi read failed: addr=%03X, rc=%d\n", |
| INT_RT_STS(chip->mem_base), rc); |
| else |
| pr_info("memif rt_sts: 0x%x\n", rt_sts); |
| |
| rc = fg_mem_read(chip, buffer, SRAM_DUMP_START, SRAM_DUMP_LEN, 0, 0); |
| if (rc) { |
| pr_err("dump failed: rc = %d\n", rc); |
| return; |
| } |
| |
| for (i = 0; i < SRAM_DUMP_LEN; i += 4) { |
| str[0] = '\0'; |
| fill_string(str, DEBUG_PRINT_BUFFER_SIZE, buffer + i, 4); |
| pr_info("%03X %s\n", SRAM_DUMP_START + i, str); |
| } |
| devm_kfree(chip->dev, buffer); |
| } |
| |
| #define MAXRSCHANGE_REG 0x434 |
| #define ESR_VALUE_OFFSET 1 |
| #define ESR_STRICT_VALUE 0x4120391F391F3019 |
| #define ESR_DEFAULT_VALUE 0x58CD4A6761C34A67 |
| static void update_esr_value(struct work_struct *work) |
| { |
| union power_supply_propval prop = {0, }; |
| u64 esr_value; |
| int rc = 0; |
| struct fg_chip *chip = container_of(work, |
| struct fg_chip, |
| update_esr_work); |
| |
| if (!chip->batt_psy && chip->batt_psy_name) |
| chip->batt_psy = power_supply_get_by_name(chip->batt_psy_name); |
| |
| if (chip->batt_psy) |
| chip->batt_psy->get_property(chip->batt_psy, |
| POWER_SUPPLY_PROP_CHARGE_TYPE, &prop); |
| else |
| return; |
| |
| if (!chip->esr_strict_filter) { |
| if ((prop.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER && |
| chip->status == POWER_SUPPLY_STATUS_CHARGING) || |
| (chip->status == POWER_SUPPLY_STATUS_FULL)) { |
| esr_value = ESR_STRICT_VALUE; |
| rc = fg_mem_write(chip, (u8 *)&esr_value, |
| MAXRSCHANGE_REG, 8, |
| ESR_VALUE_OFFSET, 0); |
| if (rc) |
| pr_err("failed to write strict ESR value rc=%d\n", |
| rc); |
| else |
| chip->esr_strict_filter = true; |
| } |
| } else if ((prop.intval != POWER_SUPPLY_CHARGE_TYPE_TAPER && |
| chip->status == POWER_SUPPLY_STATUS_CHARGING) || |
| (chip->status == POWER_SUPPLY_STATUS_DISCHARGING)) { |
| esr_value = ESR_DEFAULT_VALUE; |
| rc = fg_mem_write(chip, (u8 *)&esr_value, MAXRSCHANGE_REG, 8, |
| ESR_VALUE_OFFSET, 0); |
| if (rc) |
| pr_err("failed to write default ESR value rc=%d\n", rc); |
| else |
| chip->esr_strict_filter = false; |
| } |
| } |
| |
| #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 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); |
| goto out; |
| } |
| if (!vbatt_low_sts && chip->vbat_low_irq_enabled) { |
| if (fg_debug_mask & FG_IRQS) |
| pr_info("disabling vbatt_low irq\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; |
| } |
| } |
| if (chip->power_supply_registered) |
| power_supply_changed(&chip->bms_psy); |
| out: |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t fg_batt_missing_irq_handler(int irq, void *_chip) |
| { |
| struct fg_chip *chip = _chip; |
| bool batt_missing = is_battery_missing(chip); |
| |
| if (batt_missing) { |
| chip->battery_missing = true; |
| chip->profile_loaded = false; |
| chip->batt_type = missing_batt_type; |
| mutex_lock(&chip->cyc_ctr.lock); |
| if (fg_debug_mask & FG_IRQS) |
| pr_info("battery missing, clearing cycle counters\n"); |
| clear_cycle_counter(chip); |
| mutex_unlock(&chip->cyc_ctr.lock); |
| } else { |
| if (!chip->use_otp_profile) { |
| INIT_COMPLETION(chip->batt_id_avail); |
| schedule_work(&chip->batt_profile_init); |
| cancel_delayed_work(&chip->update_sram_data); |
| schedule_delayed_work( |
| &chip->update_sram_data, |
| msecs_to_jiffies(0)); |
| } else { |
| chip->battery_missing = false; |
| } |
| } |
| |
| if (fg_debug_mask & FG_IRQS) |
| pr_info("batt-missing triggered: %s\n", |
| batt_missing ? "missing" : "present"); |
| |
| if (chip->power_supply_registered) |
| power_supply_changed(&chip->bms_psy); |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t fg_mem_avail_irq_handler(int irq, void *_chip) |
| { |
| struct fg_chip *chip = _chip; |
| u8 mem_if_sts; |
| int rc; |
| |
| rc = fg_read(chip, &mem_if_sts, INT_RT_STS(chip->mem_base), 1); |
| if (rc) { |
| pr_err("failed to read mem status rc=%d\n", rc); |
| return IRQ_HANDLED; |
| } |
| |
| if (fg_check_sram_access(chip)) { |
| if ((fg_debug_mask & FG_IRQS) |
| & (FG_MEM_DEBUG_READS | FG_MEM_DEBUG_WRITES)) |
| pr_info("sram access granted\n"); |
| INIT_COMPLETION(chip->sram_access_revoked); |
| complete_all(&chip->sram_access_granted); |
| } else { |
| if ((fg_debug_mask & FG_IRQS) |
| & (FG_MEM_DEBUG_READS | FG_MEM_DEBUG_WRITES)) |
| pr_info("sram access revoked\n"); |
| complete_all(&chip->sram_access_revoked); |
| } |
| |
| if (!rc && (fg_debug_mask & FG_IRQS) |
| & (FG_MEM_DEBUG_READS | FG_MEM_DEBUG_WRITES)) |
| pr_info("mem_if sts 0x%02x\n", mem_if_sts); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t fg_soc_irq_handler(int irq, void *_chip) |
| { |
| struct fg_chip *chip = _chip; |
| u8 soc_rt_sts; |
| int rc; |
| |
| rc = fg_read(chip, &soc_rt_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); |
| } |
| |
| if (fg_debug_mask & FG_IRQS) |
| pr_info("triggered 0x%x\n", soc_rt_sts); |
| |
| schedule_work(&chip->battery_age_work); |
| |
| if (chip->power_supply_registered) |
| power_supply_changed(&chip->bms_psy); |
| |
| if (chip->rslow_comp.chg_rs_to_rslow > 0 && |
| chip->rslow_comp.chg_rslow_comp_c1 > 0 && |
| chip->rslow_comp.chg_rslow_comp_c2 > 0) |
| schedule_work(&chip->rslow_comp_work); |
| if (chip->cyc_ctr.en) |
| schedule_work(&chip->cycle_count_work); |
| schedule_work(&chip->update_esr_work); |
| if (chip->charge_full) |
| schedule_work(&chip->charge_full_work); |
| 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; |
| u8 soc_rt_sts; |
| int rc; |
| |
| rc = fg_read(chip, &soc_rt_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); |
| goto done; |
| } |
| |
| if (fg_debug_mask & FG_IRQS) |
| pr_info("triggered 0x%x\n", soc_rt_sts); |
| if (fg_is_batt_empty(chip)) { |
| fg_stay_awake(&chip->empty_check_wakeup_source); |
| schedule_delayed_work(&chip->check_empty_work, |
| msecs_to_jiffies(FG_EMPTY_DEBOUNCE_MS)); |
| } else { |
| chip->soc_empty = false; |
| } |
| |
| done: |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t fg_first_soc_irq_handler(int irq, void *_chip) |
| { |
| struct fg_chip *chip = _chip; |
| |
| if (fg_debug_mask & FG_IRQS) |
| pr_info("triggered\n"); |
| |
| if (fg_est_dump) |
| schedule_work(&chip->dump_sram); |
| |
| if (chip->power_supply_registered) |
| power_supply_changed(&chip->bms_psy); |
| return IRQ_HANDLED; |
| } |
| |
| static void fg_external_power_changed(struct power_supply *psy) |
| { |
| struct fg_chip *chip = container_of(psy, struct fg_chip, bms_psy); |
| |
| if (is_input_present(chip) && chip->rslow_comp.active && |
| chip->rslow_comp.chg_rs_to_rslow > 0 && |
|