| /* 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) "BMS: %s: " fmt, __func__ |
| |
| #include <linux/module.h> |
| #include <linux/types.h> |
| #include <linux/init.h> |
| #include <linux/slab.h> |
| #include <linux/err.h> |
| #include <linux/of.h> |
| #include <linux/of_device.h> |
| #include <linux/interrupt.h> |
| #include <linux/sched.h> |
| #include <linux/cdev.h> |
| #include <linux/fs.h> |
| #include <linux/delay.h> |
| #include <linux/rtc.h> |
| #include <linux/power_supply.h> |
| #include <linux/fcntl.h> |
| #include <linux/uaccess.h> |
| #include <linux/spmi.h> |
| #include <linux/wakelock.h> |
| #include <linux/debugfs.h> |
| |
| #include <linux/qpnp/power-on.h> |
| #include <linux/qpnp/qpnp-adc.h> |
| #include <linux/of_batterydata.h> |
| #include <linux/batterydata-interface.h> |
| #include <linux/qpnp-revid.h> |
| #include <uapi/linux/vm_bms.h> |
| |
| #define _BMS_MASK(BITS, POS) \ |
| ((unsigned char)(((1 << (BITS)) - 1) << (POS))) |
| #define BMS_MASK(LEFT_BIT_POS, RIGHT_BIT_POS) \ |
| _BMS_MASK((LEFT_BIT_POS) - (RIGHT_BIT_POS) + 1, \ |
| (RIGHT_BIT_POS)) |
| |
| /* Config / Data registers */ |
| #define REVISION1_REG 0x0 |
| #define STATUS1_REG 0x8 |
| #define FSM_STATE_MASK BMS_MASK(5, 3) |
| #define FSM_STATE_SHIFT 3 |
| |
| #define STATUS2_REG 0x9 |
| #define FIFO_CNT_SD_MASK BMS_MASK(7, 4) |
| #define FIFO_CNT_SD_SHIFT 4 |
| |
| #define MODE_CTL_REG 0x40 |
| #define FORCE_S3_MODE BIT(0) |
| #define ENABLE_S3_MODE BIT(1) |
| #define FORCE_S2_MODE BIT(2) |
| #define ENABLE_S2_MODE BIT(3) |
| #define S2_MODE_MASK BMS_MASK(3, 2) |
| #define S3_MODE_MASK BMS_MASK(1, 0) |
| |
| #define DATA_CTL1_REG 0x42 |
| #define MASTER_HOLD_BIT BIT(0) |
| |
| #define DATA_CTL2_REG 0x43 |
| #define FIFO_CNT_SD_CLR_BIT BIT(2) |
| #define ACC_DATA_SD_CLR_BIT BIT(1) |
| #define ACC_CNT_SD_CLR_BIT BIT(0) |
| |
| #define S3_OCV_TOL_CTL_REG 0x44 |
| |
| #define EN_CTL_REG 0x46 |
| #define BMS_EN_BIT BIT(7) |
| |
| #define FIFO_LENGTH_REG 0x47 |
| #define S1_FIFO_LENGTH_MASK BMS_MASK(3, 0) |
| #define S2_FIFO_LENGTH_MASK BMS_MASK(7, 4) |
| #define S2_FIFO_LENGTH_SHIFT 4 |
| |
| #define S1_SAMPLE_INTVL_REG 0x55 |
| #define S2_SAMPLE_INTVL_REG 0x56 |
| #define S3_SAMPLE_INTVL_REG 0x57 |
| |
| #define S1_ACC_CNT_REG 0x5E |
| #define S2_ACC_CNT_REG 0x5F |
| #define ACC_CNT_MASK BMS_MASK(2, 0) |
| |
| #define ACC_DATA0_SD_REG 0x63 |
| #define ACC_CNT_SD_REG 0x67 |
| #define OCV_DATA0_REG 0x6A |
| #define FIFO_0_LSB_REG 0xC0 |
| |
| #define BMS_SOC_REG 0xB0 |
| #define BMS_OCV_REG 0xB1 /* B1 & B2 */ |
| #define SOC_STORAGE_MASK 0xFE |
| |
| #define CHARGE_INCREASE_STORAGE 0xB3 |
| #define CHARGE_CYCLE_STORAGE_LSB 0xB4 /* B4 & B5 */ |
| |
| #define SEC_ACCESS 0xD0 |
| |
| #define QPNP_CHARGER_PRESENT BIT(7) |
| |
| /* Constants */ |
| #define OCV_TOL_LSB_UV 300 |
| #define MAX_OCV_TOL_THRESHOLD (OCV_TOL_LSB_UV * 0xFF) |
| #define MAX_SAMPLE_COUNT 256 |
| #define MAX_SAMPLE_INTERVAL 2550 |
| #define BMS_READ_TIMEOUT 500 |
| #define BMS_DEFAULT_TEMP 250 |
| #define OCV_INVALID 0xFFFF |
| #define SOC_INVALID 0xFF |
| #define OCV_UNINITIALIZED 0xFFFF |
| #define VBATT_ERROR_MARGIN 20000 |
| #define CV_DROP_MARGIN 10000 |
| #define MIN_OCV_UV 2000000 |
| #define TIME_PER_PERCENT_UUC 60 |
| #define IAVG_SAMPLES 16 |
| #define MIN_SOC_UUC 3 |
| |
| #define QPNP_VM_BMS_DEV_NAME "qcom,qpnp-vm-bms" |
| |
| /* indicates the state of BMS */ |
| enum { |
| IDLE_STATE, |
| S1_STATE, |
| S2_STATE, |
| S3_STATE, |
| S7_STATE, |
| }; |
| |
| enum { |
| WRKARND_PON_OCV_COMP = BIT(0), |
| }; |
| |
| struct bms_irq { |
| int irq; |
| unsigned long disabled; |
| }; |
| |
| struct bms_wakeup_source { |
| struct wakeup_source source; |
| unsigned long disabled; |
| }; |
| |
| struct temp_curr_comp_map { |
| int temp_decideg; |
| int current_ma; |
| }; |
| |
| struct bms_dt_cfg { |
| bool cfg_report_charger_eoc; |
| bool cfg_force_bms_active_on_charger; |
| bool cfg_force_s3_on_suspend; |
| bool cfg_ignore_shutdown_soc; |
| bool cfg_use_voltage_soc; |
| int cfg_v_cutoff_uv; |
| int cfg_max_voltage_uv; |
| int cfg_r_conn_mohm; |
| int cfg_shutdown_soc_valid_limit; |
| int cfg_low_soc_calc_threshold; |
| int cfg_low_soc_calculate_soc_ms; |
| int cfg_low_voltage_threshold; |
| int cfg_low_voltage_calculate_soc_ms; |
| int cfg_low_soc_fifo_length; |
| int cfg_calculate_soc_ms; |
| int cfg_voltage_soc_timeout_ms; |
| int cfg_s1_sample_interval_ms; |
| int cfg_s2_sample_interval_ms; |
| int cfg_s1_sample_count; |
| int cfg_s2_sample_count; |
| int cfg_s1_fifo_length; |
| int cfg_s2_fifo_length; |
| int cfg_disable_bms; |
| int cfg_s3_ocv_tol_uv; |
| int cfg_soc_resume_limit; |
| int cfg_low_temp_threshold; |
| int cfg_ibat_avg_samples; |
| int cfg_battery_aging_comp; |
| bool cfg_use_reported_soc; |
| }; |
| |
| struct qpnp_bms_chip { |
| struct device *dev; |
| struct spmi_device *spmi; |
| dev_t dev_no; |
| u16 base; |
| u8 revision[2]; |
| u32 batt_pres_addr; |
| u32 chg_pres_addr; |
| |
| /* status variables */ |
| u8 current_fsm_state; |
| bool last_soc_invalid; |
| bool warm_reset; |
| bool bms_psy_registered; |
| bool battery_full; |
| bool bms_dev_open; |
| bool data_ready; |
| bool apply_suspend_config; |
| bool in_cv_state; |
| bool low_soc_fifo_set; |
| int battery_status; |
| int calculated_soc; |
| int current_now; |
| int prev_current_now; |
| int prev_voltage_based_soc; |
| int calculate_soc_ms; |
| int voltage_soc_uv; |
| int battery_present; |
| int last_soc; |
| int last_soc_unbound; |
| int last_soc_change_sec; |
| int charge_start_tm_sec; |
| int catch_up_time_sec; |
| int delta_time_s; |
| int uuc_delta_time_s; |
| int ocv_at_100; |
| int last_ocv_uv; |
| int s2_fifo_length; |
| int last_acc; |
| int hi_power_state; |
| unsigned int vadc_v0625; |
| unsigned int vadc_v1250; |
| unsigned long tm_sec; |
| unsigned long workaround_flag; |
| unsigned long uuc_tm_sec; |
| u32 seq_num; |
| u8 shutdown_soc; |
| bool shutdown_soc_invalid; |
| u16 last_ocv_raw; |
| u32 shutdown_ocv; |
| bool suspend_data_valid; |
| int iavg_num_samples; |
| unsigned int iavg_index; |
| int iavg_samples_ma[IAVG_SAMPLES]; |
| int iavg_ma; |
| int prev_soc_uuc; |
| int eoc_reported; |
| u8 charge_increase; |
| u16 charge_cycles; |
| unsigned int start_soc; |
| unsigned int end_soc; |
| |
| struct bms_battery_data *batt_data; |
| struct bms_dt_cfg dt; |
| |
| struct dentry *debug_root; |
| struct bms_wakeup_source vbms_lv_wake_source; |
| struct bms_wakeup_source vbms_cv_wake_source; |
| struct bms_wakeup_source vbms_soc_wake_source; |
| wait_queue_head_t bms_wait_q; |
| struct delayed_work monitor_soc_work; |
| struct delayed_work voltage_soc_timeout_work; |
| struct mutex bms_data_mutex; |
| struct mutex bms_device_mutex; |
| struct mutex last_soc_mutex; |
| struct mutex state_change_mutex; |
| struct class *bms_class; |
| struct device *bms_device; |
| struct cdev bms_cdev; |
| struct qpnp_vm_bms_data bms_data; |
| struct qpnp_vadc_chip *vadc_dev; |
| struct qpnp_adc_tm_chip *adc_tm_dev; |
| struct pmic_revid_data *revid_data; |
| struct qpnp_adc_tm_btm_param vbat_monitor_params; |
| struct bms_irq fifo_update_done_irq; |
| struct bms_irq fsm_state_change_irq; |
| struct power_supply bms_psy; |
| struct power_supply *batt_psy; |
| struct power_supply *usb_psy; |
| bool reported_soc_in_use; |
| bool charger_removed_since_full; |
| bool charger_reinserted; |
| bool reported_soc_high_current; |
| int reported_soc; |
| int reported_soc_change_sec; |
| int reported_soc_delta; |
| }; |
| |
| static struct qpnp_bms_chip *the_chip; |
| |
| static struct temp_curr_comp_map temp_curr_comp_lut[] = { |
| {-300, 15}, |
| {250, 17}, |
| {850, 28}, |
| }; |
| |
| static void disable_bms_irq(struct bms_irq *irq) |
| { |
| if (!__test_and_set_bit(0, &irq->disabled)) { |
| disable_irq(irq->irq); |
| pr_debug("disabled irq %d\n", irq->irq); |
| } |
| } |
| |
| static void bms_stay_awake(struct bms_wakeup_source *source) |
| { |
| if (__test_and_clear_bit(0, &source->disabled)) { |
| __pm_stay_awake(&source->source); |
| pr_debug("enabled source %s\n", source->source.name); |
| } |
| } |
| |
| static void bms_relax(struct bms_wakeup_source *source) |
| { |
| if (!__test_and_set_bit(0, &source->disabled)) { |
| __pm_relax(&source->source); |
| pr_debug("disabled source %s\n", source->source.name); |
| } |
| } |
| |
| static bool bms_wake_active(struct bms_wakeup_source *source) |
| { |
| return !source->disabled; |
| } |
| |
| static int bound_soc(int soc) |
| { |
| soc = max(0, soc); |
| soc = min(100, soc); |
| |
| return soc; |
| } |
| |
| static char *qpnp_vm_bms_supplicants[] = { |
| "battery", |
| }; |
| |
| static int qpnp_read_wrapper(struct qpnp_bms_chip *chip, u8 *val, |
| u16 base, int count) |
| { |
| int rc; |
| struct spmi_device *spmi = chip->spmi; |
| |
| rc = spmi_ext_register_readl(spmi->ctrl, spmi->sid, base, val, count); |
| if (rc) |
| pr_err("SPMI read failed rc=%d\n", rc); |
| |
| return rc; |
| } |
| |
| static int qpnp_write_wrapper(struct qpnp_bms_chip *chip, u8 *val, |
| u16 base, int count) |
| { |
| int rc; |
| struct spmi_device *spmi = chip->spmi; |
| |
| rc = spmi_ext_register_writel(spmi->ctrl, spmi->sid, base, val, count); |
| if (rc) |
| pr_err("SPMI write failed rc=%d\n", rc); |
| |
| return rc; |
| } |
| |
| static int qpnp_masked_write_base(struct qpnp_bms_chip *chip, u16 addr, |
| u8 mask, u8 val) |
| { |
| int rc; |
| u8 reg; |
| |
| rc = qpnp_read_wrapper(chip, ®, addr, 1); |
| if (rc) { |
| pr_err("read failed addr = %03X, rc = %d\n", addr, rc); |
| return rc; |
| } |
| reg &= ~mask; |
| reg |= val & mask; |
| rc = qpnp_write_wrapper(chip, ®, addr, 1); |
| if (rc) |
| pr_err("write failed addr = %03X, val = %02x, mask = %02x, reg = %02x, rc = %d\n", |
| addr, val, mask, reg, rc); |
| |
| return rc; |
| } |
| |
| static int qpnp_secure_write_wrapper(struct qpnp_bms_chip *chip, u8 *val, |
| u16 base) |
| { |
| int rc; |
| u8 reg; |
| |
| reg = 0xA5; |
| rc = qpnp_write_wrapper(chip, ®, chip->base + SEC_ACCESS, 1); |
| if (rc) { |
| pr_err("Error %d writing 0xA5 to 0x%x reg\n", |
| rc, SEC_ACCESS); |
| return rc; |
| } |
| rc = qpnp_write_wrapper(chip, val, base, 1); |
| if (rc) |
| pr_err("Error %d writing %d to 0x%x reg\n", rc, *val, base); |
| |
| return rc; |
| } |
| |
| static int backup_ocv_soc(struct qpnp_bms_chip *chip, int ocv_uv, int soc) |
| { |
| int rc; |
| u16 ocv_mv = ocv_uv / 1000; |
| |
| rc = qpnp_write_wrapper(chip, (u8 *)&ocv_mv, |
| chip->base + BMS_OCV_REG, 2); |
| if (rc) |
| pr_err("Unable to backup OCV rc=%d\n", rc); |
| |
| rc = qpnp_masked_write_base(chip, chip->base + BMS_SOC_REG, |
| SOC_STORAGE_MASK, (soc + 1) << 1); |
| if (rc) |
| pr_err("Unable to backup SOC rc=%d\n", rc); |
| |
| pr_debug("ocv_mv=%d soc=%d\n", ocv_mv, soc); |
| |
| return rc; |
| } |
| |
| 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; |
| } |
| |
| static int calculate_delta_time(unsigned long *time_stamp, int *delta_time_s) |
| { |
| unsigned long now_tm_sec = 0; |
| |
| /* default to delta time = 0 if anything fails */ |
| *delta_time_s = 0; |
| |
| if (get_current_time(&now_tm_sec)) { |
| pr_err("RTC read failed\n"); |
| return 0; |
| } |
| |
| *delta_time_s = (now_tm_sec - *time_stamp); |
| |
| /* remember this time */ |
| *time_stamp = now_tm_sec; |
| return 0; |
| } |
| |
| static bool is_charger_present(struct qpnp_bms_chip *chip) |
| { |
| union power_supply_propval ret = {0,}; |
| |
| if (chip->usb_psy == NULL) |
| 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, &ret); |
| return ret.intval; |
| } |
| |
| return false; |
| } |
| |
| static bool is_battery_charging(struct qpnp_bms_chip *chip) |
| { |
| union power_supply_propval ret = {0,}; |
| |
| if (chip->batt_psy == NULL) |
| chip->batt_psy = power_supply_get_by_name("battery"); |
| if (chip->batt_psy) { |
| /* if battery has been registered, use the type property */ |
| chip->batt_psy->get_property(chip->batt_psy, |
| POWER_SUPPLY_PROP_CHARGE_TYPE, &ret); |
| return ret.intval != POWER_SUPPLY_CHARGE_TYPE_NONE; |
| } |
| |
| /* Default to false if the battery power supply is not registered. */ |
| pr_debug("battery power supply is not registered\n"); |
| return false; |
| } |
| |
| #define BAT_PRES_BIT BIT(7) |
| static bool is_battery_present(struct qpnp_bms_chip *chip) |
| { |
| union power_supply_propval ret = {0,}; |
| int rc; |
| u8 batt_pres; |
| |
| /* first try to use the batt_pres register if given */ |
| if (chip->batt_pres_addr) { |
| rc = qpnp_read_wrapper(chip, &batt_pres, |
| chip->batt_pres_addr, 1); |
| if (!rc && (batt_pres & BAT_PRES_BIT)) |
| return true; |
| else |
| return false; |
| } |
| if (chip->batt_psy == NULL) |
| chip->batt_psy = power_supply_get_by_name("battery"); |
| if (chip->batt_psy) { |
| /* if battery has been registered, use the present property */ |
| chip->batt_psy->get_property(chip->batt_psy, |
| POWER_SUPPLY_PROP_PRESENT, &ret); |
| return ret.intval; |
| } |
| |
| /* Default to false if the battery power supply is not registered. */ |
| pr_debug("battery power supply is not registered\n"); |
| return false; |
| } |
| |
| #define BAT_REMOVED_OFFMODE_BIT BIT(6) |
| static bool is_battery_replaced_in_offmode(struct qpnp_bms_chip *chip) |
| { |
| u8 batt_pres; |
| int rc; |
| |
| if (chip->batt_pres_addr) { |
| rc = qpnp_read_wrapper(chip, &batt_pres, |
| chip->batt_pres_addr, 1); |
| pr_debug("offmode removed: %02x\n", batt_pres); |
| if (!rc && (batt_pres & BAT_REMOVED_OFFMODE_BIT)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static bool is_battery_taper_charging(struct qpnp_bms_chip *chip) |
| { |
| union power_supply_propval ret = {0,}; |
| |
| if (chip->batt_psy == NULL) |
| chip->batt_psy = power_supply_get_by_name("battery"); |
| |
| if (chip->batt_psy) { |
| chip->batt_psy->get_property(chip->batt_psy, |
| POWER_SUPPLY_PROP_CHARGE_TYPE, &ret); |
| return ret.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER; |
| } |
| |
| return false; |
| } |
| |
| static int master_hold_control(struct qpnp_bms_chip *chip, bool enable) |
| { |
| u8 reg = 0; |
| int rc; |
| |
| reg = enable ? MASTER_HOLD_BIT : 0; |
| |
| rc = qpnp_secure_write_wrapper(chip, ®, |
| chip->base + DATA_CTL1_REG); |
| if (rc) |
| pr_err("Unable to write reg=%x rc=%d\n", DATA_CTL1_REG, rc); |
| |
| return rc; |
| } |
| |
| static int force_fsm_state(struct qpnp_bms_chip *chip, u8 state) |
| { |
| int rc; |
| u8 mode_ctl = 0; |
| |
| switch (state) { |
| case S2_STATE: |
| mode_ctl = (FORCE_S2_MODE | ENABLE_S2_MODE); |
| break; |
| case S3_STATE: |
| mode_ctl = (FORCE_S3_MODE | ENABLE_S3_MODE); |
| break; |
| default: |
| pr_debug("Invalid state %d\n", state); |
| return -EINVAL; |
| } |
| |
| rc = qpnp_secure_write_wrapper(chip, &mode_ctl, |
| chip->base + MODE_CTL_REG); |
| if (rc) { |
| pr_err("Unable to write reg=%x rc=%d\n", MODE_CTL_REG, rc); |
| return rc; |
| } |
| /* delay for the FSM state to take affect in hardware */ |
| usleep_range(500, 600); |
| |
| pr_debug("force_mode=%d mode_cntl_reg=%x\n", state, mode_ctl); |
| |
| return 0; |
| } |
| |
| static int get_sample_interval(struct qpnp_bms_chip *chip, |
| u8 fsm_state, u32 *interval) |
| { |
| int rc; |
| u8 val = 0, reg; |
| |
| *interval = 0; |
| |
| switch (fsm_state) { |
| case S1_STATE: |
| reg = S1_SAMPLE_INTVL_REG; |
| break; |
| case S2_STATE: |
| reg = S2_SAMPLE_INTVL_REG; |
| break; |
| case S3_STATE: |
| reg = S3_SAMPLE_INTVL_REG; |
| break; |
| default: |
| pr_err("Invalid state %d\n", fsm_state); |
| return -EINVAL; |
| } |
| |
| rc = qpnp_read_wrapper(chip, &val, chip->base + reg, 1); |
| if (rc) { |
| pr_err("Failed to get state(%d) sample_interval, rc=%d\n", |
| fsm_state, rc); |
| return rc; |
| } |
| |
| *interval = val * 10; |
| |
| return 0; |
| } |
| |
| static int get_sample_count(struct qpnp_bms_chip *chip, |
| u8 fsm_state, u32 *count) |
| { |
| int rc; |
| u8 val = 0, reg; |
| |
| *count = 0; |
| |
| switch (fsm_state) { |
| case S1_STATE: |
| reg = S1_ACC_CNT_REG; |
| break; |
| case S2_STATE: |
| reg = S2_ACC_CNT_REG; |
| break; |
| default: |
| pr_err("Invalid state %d\n", fsm_state); |
| return -EINVAL; |
| } |
| |
| rc = qpnp_read_wrapper(chip, &val, chip->base + reg, 1); |
| if (rc) { |
| pr_err("Failed to get state(%d) sample_count, rc=%d\n", |
| fsm_state, rc); |
| return rc; |
| } |
| val &= ACC_CNT_MASK; |
| |
| *count = val ? (1 << (val + 1)) : 1; |
| |
| return 0; |
| } |
| |
| static int get_fifo_length(struct qpnp_bms_chip *chip, |
| u8 fsm_state, u32 *fifo_length) |
| { |
| int rc; |
| u8 val = 0, reg, mask = 0, shift = 0; |
| |
| *fifo_length = 0; |
| |
| switch (fsm_state) { |
| case S1_STATE: |
| reg = FIFO_LENGTH_REG; |
| mask = S1_FIFO_LENGTH_MASK; |
| shift = 0; |
| break; |
| case S2_STATE: |
| reg = FIFO_LENGTH_REG; |
| mask = S2_FIFO_LENGTH_MASK; |
| shift = S2_FIFO_LENGTH_SHIFT; |
| break; |
| default: |
| pr_err("Invalid state %d\n", fsm_state); |
| return -EINVAL; |
| } |
| |
| rc = qpnp_read_wrapper(chip, &val, chip->base + reg, 1); |
| if (rc) { |
| pr_err("Failed to get state(%d) fifo_length, rc=%d\n", |
| fsm_state, rc); |
| return rc; |
| } |
| |
| val &= mask; |
| val >>= shift; |
| |
| *fifo_length = val; |
| |
| return 0; |
| } |
| |
| static int set_fifo_length(struct qpnp_bms_chip *chip, |
| u8 fsm_state, u32 fifo_length) |
| { |
| int rc; |
| u8 reg, mask = 0, shift = 0; |
| |
| /* fifo_length of 1 is not supported due to a hardware issue */ |
| if ((fifo_length <= 1) || (fifo_length > MAX_FIFO_REGS)) { |
| pr_err("Invalid FIFO length = %d\n", fifo_length); |
| return -EINVAL; |
| } |
| |
| switch (fsm_state) { |
| case S1_STATE: |
| reg = FIFO_LENGTH_REG; |
| mask = S1_FIFO_LENGTH_MASK; |
| shift = 0; |
| break; |
| case S2_STATE: |
| reg = FIFO_LENGTH_REG; |
| mask = S2_FIFO_LENGTH_MASK; |
| shift = S2_FIFO_LENGTH_SHIFT; |
| break; |
| default: |
| pr_err("Invalid state %d\n", fsm_state); |
| return -EINVAL; |
| } |
| |
| rc = master_hold_control(chip, true); |
| if (rc) |
| pr_err("Unable to apply master_hold rc=%d\n", rc); |
| |
| rc = qpnp_masked_write_base(chip, chip->base + reg, mask, |
| fifo_length << shift); |
| if (rc) |
| pr_err("Unable to set fifo length rc=%d\n", rc); |
| |
| rc = master_hold_control(chip, false); |
| if (rc) |
| pr_err("Unable to apply master_hold rc=%d\n", rc); |
| |
| return rc; |
| } |
| |
| static int get_fsm_state(struct qpnp_bms_chip *chip, u8 *state) |
| { |
| int rc; |
| |
| /* |
| * To read the STATUS1 register, write a value(any) to this register, |
| * wait for 10ms and then read the register. |
| */ |
| *state = 0; |
| rc = qpnp_write_wrapper(chip, state, chip->base + STATUS1_REG, 1); |
| if (rc) { |
| pr_err("Unable to write STATUS1_REG rc=%d\n", rc); |
| return rc; |
| } |
| usleep_range(10000, 11000); |
| |
| /* read the current FSM state */ |
| rc = qpnp_read_wrapper(chip, state, chip->base + STATUS1_REG, 1); |
| if (rc) { |
| pr_err("Unable to read STATUS1_REG rc=%d\n", rc); |
| return rc; |
| } |
| *state = (*state & FSM_STATE_MASK) >> FSM_STATE_SHIFT; |
| |
| return rc; |
| } |
| |
| static int update_fsm_state(struct qpnp_bms_chip *chip) |
| { |
| u8 state = 0; |
| int rc; |
| |
| mutex_lock(&chip->state_change_mutex); |
| rc = get_fsm_state(chip, &state); |
| if (rc) { |
| pr_err("Unable to get fsm_state rc=%d\n", rc); |
| goto fail_fsm; |
| } |
| |
| chip->current_fsm_state = state; |
| |
| fail_fsm: |
| mutex_unlock(&chip->state_change_mutex); |
| return rc; |
| } |
| |
| static int backup_charge_cycle(struct qpnp_bms_chip *chip) |
| { |
| int rc = 0; |
| |
| if (chip->charge_increase >= 0) { |
| rc = qpnp_write_wrapper(chip, &chip->charge_increase, |
| chip->base + CHARGE_INCREASE_STORAGE, 1); |
| if (rc) |
| pr_err("Unable to backup charge_increase rc=%d\n", rc); |
| } |
| |
| if (chip->charge_cycles >= 0) { |
| rc = qpnp_write_wrapper(chip, (u8 *)&chip->charge_cycles, |
| chip->base + CHARGE_CYCLE_STORAGE_LSB, 2); |
| if (rc) |
| pr_err("Unable to backup charge_cycles rc=%d\n", rc); |
| } |
| |
| pr_debug("%s storing charge_increase=%u charge_cycle=%u\n", |
| rc ? "Unable to" : "Sucessfully", |
| chip->charge_increase, chip->charge_cycles); |
| |
| return rc; |
| } |
| |
| static int read_chgcycle_data_from_backup(struct qpnp_bms_chip *chip) |
| { |
| int rc; |
| uint16_t temp_u16 = 0; |
| u8 temp_u8 = 0; |
| |
| rc = qpnp_read_wrapper(chip, &temp_u8, |
| chip->base + CHARGE_INCREASE_STORAGE, 1); |
| if (rc) { |
| pr_err("Unable to read charge_increase rc=%d\n", rc); |
| return rc; |
| } |
| |
| rc = qpnp_read_wrapper(chip, (u8 *)&temp_u16, |
| chip->base + CHARGE_CYCLE_STORAGE_LSB, 2); |
| if (rc) { |
| pr_err("Unable to read charge_cycle rc=%d\n", rc); |
| return rc; |
| } |
| |
| if ((temp_u8 == 0xFF) || (temp_u16 == 0xFFFF)) { |
| chip->charge_cycles = 0; |
| chip->charge_increase = 0; |
| pr_info("rejecting aging data charge_increase=%u charge_cycle=%u\n", |
| temp_u8, temp_u16); |
| rc = backup_charge_cycle(chip); |
| if (rc) |
| pr_err("Unable to reset charge cycles rc=%d\n", rc); |
| } else { |
| chip->charge_increase = temp_u8; |
| chip->charge_cycles = temp_u16; |
| } |
| |
| pr_debug("charge_increase=%u charge_cycle=%u\n", |
| chip->charge_increase, chip->charge_cycles); |
| return rc; |
| } |
| |
| static int calculate_uuc_iavg(struct qpnp_bms_chip *chip) |
| { |
| int i; |
| int iavg_ma = chip->current_now / 1000; |
| |
| /* only continue if ibat has changed */ |
| if (chip->current_now == chip->prev_current_now) |
| goto ibat_unchanged; |
| else |
| chip->prev_current_now = chip->current_now; |
| |
| chip->iavg_samples_ma[chip->iavg_index] = iavg_ma; |
| chip->iavg_index = (chip->iavg_index + 1) % |
| chip->dt.cfg_ibat_avg_samples; |
| chip->iavg_num_samples++; |
| if (chip->iavg_num_samples >= chip->dt.cfg_ibat_avg_samples) |
| chip->iavg_num_samples = chip->dt.cfg_ibat_avg_samples; |
| |
| if (chip->iavg_num_samples) { |
| iavg_ma = 0; |
| /* maintain a 16 sample average of ibat */ |
| for (i = 0; i < chip->iavg_num_samples; i++) { |
| pr_debug("iavg_samples_ma[%d] = %d\n", i, |
| chip->iavg_samples_ma[i]); |
| iavg_ma += chip->iavg_samples_ma[i]; |
| } |
| |
| chip->iavg_ma = DIV_ROUND_CLOSEST(iavg_ma, |
| chip->iavg_num_samples); |
| } |
| |
| ibat_unchanged: |
| pr_debug("current_now_ma=%d averaged_iavg_ma=%d\n", |
| chip->current_now / 1000, chip->iavg_ma); |
| |
| return chip->iavg_ma; |
| } |
| |
| static int adjust_uuc(struct qpnp_bms_chip *chip, int soc_uuc) |
| { |
| int max_percent_change; |
| |
| calculate_delta_time(&chip->uuc_tm_sec, &chip->uuc_delta_time_s); |
| |
| /* make sure that the UUC changes 1% at a time */ |
| max_percent_change = max(chip->uuc_delta_time_s |
| / TIME_PER_PERCENT_UUC, 1); |
| |
| if (chip->prev_soc_uuc == -EINVAL) { |
| /* start with a minimum UUC if the initial UUC is high */ |
| if (soc_uuc > MIN_SOC_UUC) |
| chip->prev_soc_uuc = MIN_SOC_UUC; |
| else |
| chip->prev_soc_uuc = soc_uuc; |
| } else { |
| if (abs(chip->prev_soc_uuc - soc_uuc) <= max_percent_change) |
| chip->prev_soc_uuc = soc_uuc; |
| else if (soc_uuc > chip->prev_soc_uuc) |
| chip->prev_soc_uuc += max_percent_change; |
| else |
| chip->prev_soc_uuc -= max_percent_change; |
| } |
| |
| pr_debug("soc_uuc=%d new_soc_uuc=%d\n", soc_uuc, chip->prev_soc_uuc); |
| |
| return chip->prev_soc_uuc; |
| } |
| |
| static int lookup_soc_ocv(struct qpnp_bms_chip *chip, int ocv_uv, int batt_temp) |
| { |
| int soc_ocv = 0, soc_cutoff = 0, soc_final = 0; |
| int fcc, acc, soc_uuc = 0, soc_acc = 0, iavg_ma = 0; |
| |
| soc_ocv = interpolate_pc(chip->batt_data->pc_temp_ocv_lut, |
| batt_temp, ocv_uv / 1000); |
| soc_cutoff = interpolate_pc(chip->batt_data->pc_temp_ocv_lut, |
| batt_temp, chip->dt.cfg_v_cutoff_uv / 1000); |
| |
| soc_final = DIV_ROUND_CLOSEST(100 * (soc_ocv - soc_cutoff), |
| (100 - soc_cutoff)); |
| |
| if (chip->batt_data->ibat_acc_lut) { |
| /* Apply ACC logic only if we discharging */ |
| if (!is_battery_charging(chip) && chip->current_now > 0) { |
| |
| /* |
| * IBAT averaging is disabled at low temp. |
| * allowing the SOC to catcup quickly. |
| */ |
| if (batt_temp > chip->dt.cfg_low_temp_threshold) |
| iavg_ma = calculate_uuc_iavg(chip); |
| else |
| iavg_ma = chip->current_now / 1000; |
| |
| fcc = interpolate_fcc(chip->batt_data->fcc_temp_lut, |
| batt_temp); |
| acc = interpolate_acc(chip->batt_data->ibat_acc_lut, |
| batt_temp, iavg_ma); |
| if (acc <= 0) { |
| if (chip->last_acc) |
| acc = chip->last_acc; |
| else |
| acc = fcc; |
| } |
| soc_uuc = ((fcc - acc) * 100) / fcc; |
| |
| if (batt_temp > chip->dt.cfg_low_temp_threshold) |
| soc_uuc = adjust_uuc(chip, soc_uuc); |
| |
| soc_acc = DIV_ROUND_CLOSEST(100 * (soc_ocv - soc_uuc), |
| (100 - soc_uuc)); |
| |
| pr_debug("fcc=%d acc=%d soc_final=%d soc_uuc=%d soc_acc=%d current_now=%d iavg_ma=%d\n", |
| fcc, acc, soc_final, soc_uuc, |
| soc_acc, chip->current_now / 1000, iavg_ma); |
| |
| soc_final = soc_acc; |
| chip->last_acc = acc; |
| } else { |
| /* charging - reset all the counters */ |
| chip->last_acc = 0; |
| chip->iavg_num_samples = 0; |
| chip->iavg_index = 0; |
| chip->iavg_ma = 0; |
| chip->prev_current_now = 0; |
| chip->prev_soc_uuc = -EINVAL; |
| } |
| } |
| |
| soc_final = bound_soc(soc_final); |
| |
| pr_debug("soc_final=%d soc_ocv=%d soc_cutoff=%d ocv_uv=%u batt_temp=%d\n", |
| soc_final, soc_ocv, soc_cutoff, ocv_uv, batt_temp); |
| |
| return soc_final; |
| } |
| |
| #define V_PER_BIT_MUL_FACTOR 97656 |
| #define V_PER_BIT_DIV_FACTOR 1000 |
| #define VADC_INTRINSIC_OFFSET 0x6000 |
| static int vadc_reading_to_uv(int reading, bool vadc_bms) |
| { |
| int64_t value; |
| |
| if (!vadc_bms) { |
| /* |
| * All the BMS H/W VADC values are pre-compensated |
| * for VADC_INTRINSIC_OFFSET, subtract this offset |
| * only if this reading is not obtained from BMS |
| */ |
| |
| if (reading <= VADC_INTRINSIC_OFFSET) |
| return 0; |
| |
| reading -= VADC_INTRINSIC_OFFSET; |
| } |
| |
| value = (reading * V_PER_BIT_MUL_FACTOR); |
| |
| return div_u64(value, (u32)V_PER_BIT_DIV_FACTOR); |
| } |
| |
| static int get_calculation_delay_ms(struct qpnp_bms_chip *chip) |
| { |
| if (bms_wake_active(&chip->vbms_lv_wake_source)) |
| return chip->dt.cfg_low_voltage_calculate_soc_ms; |
| if (chip->calculated_soc < chip->dt.cfg_low_soc_calc_threshold) |
| return chip->dt.cfg_low_soc_calculate_soc_ms; |
| else |
| return chip->dt.cfg_calculate_soc_ms; |
| } |
| |
| #define VADC_CALIB_UV 625000 |
| #define VBATT_MUL_FACTOR 3 |
| static int adjust_vbatt_reading(struct qpnp_bms_chip *chip, int reading_uv) |
| { |
| s64 numerator, denominator; |
| |
| if (reading_uv == 0) |
| return 0; |
| |
| /* don't adjust if not calibrated */ |
| if (chip->vadc_v0625 == 0 || chip->vadc_v1250 == 0) { |
| pr_debug("No cal yet return %d\n", |
| VBATT_MUL_FACTOR * reading_uv); |
| return VBATT_MUL_FACTOR * reading_uv; |
| } |
| |
| numerator = ((s64)reading_uv - chip->vadc_v0625) * VADC_CALIB_UV; |
| denominator = (s64)chip->vadc_v1250 - chip->vadc_v0625; |
| |
| if (denominator == 0) |
| return reading_uv * VBATT_MUL_FACTOR; |
| |
| return (VADC_CALIB_UV + div_s64(numerator, denominator)) |
| * VBATT_MUL_FACTOR; |
| } |
| |
| static int calib_vadc(struct qpnp_bms_chip *chip) |
| { |
| int rc, raw_0625, raw_1250; |
| struct qpnp_vadc_result result; |
| |
| rc = qpnp_vadc_read(chip->vadc_dev, REF_625MV, &result); |
| if (rc) { |
| pr_debug("vadc read failed with rc = %d\n", rc); |
| return rc; |
| } |
| raw_0625 = result.adc_code; |
| |
| rc = qpnp_vadc_read(chip->vadc_dev, REF_125V, &result); |
| if (rc) { |
| pr_debug("vadc read failed with rc = %d\n", rc); |
| return rc; |
| } |
| raw_1250 = result.adc_code; |
| |
| chip->vadc_v0625 = vadc_reading_to_uv(raw_0625, false); |
| chip->vadc_v1250 = vadc_reading_to_uv(raw_1250, false); |
| |
| pr_debug("vadc calib: 0625=%d raw (%d uv), 1250=%d raw (%d uv)\n", |
| raw_0625, chip->vadc_v0625, raw_1250, chip->vadc_v1250); |
| |
| return 0; |
| } |
| |
| static int convert_vbatt_raw_to_uv(struct qpnp_bms_chip *chip, |
| u16 reading, bool is_pon_ocv) |
| { |
| int64_t uv, vbatt; |
| int rc; |
| |
| uv = vadc_reading_to_uv(reading, true); |
| pr_debug("%u raw converted into %lld uv\n", reading, uv); |
| |
| uv = adjust_vbatt_reading(chip, uv); |
| pr_debug("adjusted into %lld uv\n", uv); |
| |
| vbatt = uv; |
| rc = qpnp_vbat_sns_comp_result(chip->vadc_dev, &uv, is_pon_ocv); |
| if (rc) { |
| pr_debug("Vbatt compensation failed rc = %d\n", rc); |
| uv = vbatt; |
| } else { |
| pr_debug("temp-compensated %lld into %lld uv\n", vbatt, uv); |
| } |
| |
| return uv; |
| } |
| |
| static void convert_and_store_ocv(struct qpnp_bms_chip *chip, |
| int batt_temp, bool is_pon_ocv) |
| { |
| int rc; |
| |
| rc = calib_vadc(chip); |
| if (rc) |
| pr_err("Vadc reference voltage read failed, rc = %d\n", rc); |
| |
| chip->last_ocv_uv = convert_vbatt_raw_to_uv(chip, |
| chip->last_ocv_raw, is_pon_ocv); |
| |
| pr_debug("last_ocv_uv = %d\n", chip->last_ocv_uv); |
| } |
| |
| static int read_and_update_ocv(struct qpnp_bms_chip *chip, int batt_temp, |
| bool is_pon_ocv) |
| { |
| int rc, ocv_uv; |
| u16 ocv_data = 0; |
| |
| /* read the BMS h/w OCV */ |
| rc = qpnp_read_wrapper(chip, (u8 *)&ocv_data, |
| chip->base + OCV_DATA0_REG, 2); |
| if (rc) { |
| pr_err("Error reading ocv: rc = %d\n", rc); |
| return -ENXIO; |
| } |
| |
| /* check if OCV is within limits */ |
| ocv_uv = convert_vbatt_raw_to_uv(chip, ocv_data, is_pon_ocv); |
| if (ocv_uv < MIN_OCV_UV) { |
| pr_err("OCV too low or invalid (%d)- rejecting it\n", ocv_uv); |
| return 0; |
| } |
| |
| if ((chip->last_ocv_raw == OCV_UNINITIALIZED) || |
| (chip->last_ocv_raw != ocv_data)) { |
| pr_debug("new OCV!\n"); |
| chip->last_ocv_raw = ocv_data; |
| convert_and_store_ocv(chip, batt_temp, is_pon_ocv); |
| } |
| |
| pr_debug("ocv_raw=0x%x last_ocv_raw=0x%x last_ocv_uv=%d\n", |
| ocv_data, chip->last_ocv_raw, chip->last_ocv_uv); |
| |
| return 0; |
| } |
| |
| static int get_battery_voltage(struct qpnp_bms_chip *chip, int *result_uv) |
| { |
| int rc; |
| struct qpnp_vadc_result adc_result; |
| |
| rc = qpnp_vadc_read(chip->vadc_dev, VBAT_SNS, &adc_result); |
| if (rc) { |
| pr_err("error reading adc channel = %d, rc = %d\n", |
| VBAT_SNS, rc); |
| return rc; |
| } |
| pr_debug("mvolts phy=%lld meas=0x%llx\n", adc_result.physical, |
| adc_result.measurement); |
| *result_uv = (int)adc_result.physical; |
| |
| return 0; |
| } |
| |
| static int get_battery_status(struct qpnp_bms_chip *chip) |
| { |
| union power_supply_propval ret = {0,}; |
| |
| if (chip->batt_psy == NULL) |
| chip->batt_psy = power_supply_get_by_name("battery"); |
| if (chip->batt_psy) { |
| /* if battery has been registered, use the status property */ |
| chip->batt_psy->get_property(chip->batt_psy, |
| POWER_SUPPLY_PROP_STATUS, &ret); |
| return ret.intval; |
| } |
| |
| /* Default to false if the battery power supply is not registered. */ |
| pr_debug("battery power supply is not registered\n"); |
| return POWER_SUPPLY_STATUS_UNKNOWN; |
| } |
| |
| static int get_batt_therm(struct qpnp_bms_chip *chip, int *batt_temp) |
| { |
| int rc; |
| struct qpnp_vadc_result result; |
| |
| rc = qpnp_vadc_read(chip->vadc_dev, LR_MUX1_BATT_THERM, &result); |
| if (rc) { |
| pr_err("error reading adc channel = %d, rc = %d\n", |
| LR_MUX1_BATT_THERM, rc); |
| return rc; |
| } |
| pr_debug("batt_temp phy = %lld meas = 0x%llx\n", |
| result.physical, result.measurement); |
| |
| *batt_temp = (int)result.physical; |
| |
| return 0; |
| } |
| |
| static int get_prop_bms_rbatt(struct qpnp_bms_chip *chip) |
| { |
| return chip->batt_data->default_rbatt_mohm; |
| } |
| |
| static int get_rbatt(struct qpnp_bms_chip *chip, int soc, int batt_temp) |
| { |
| int rbatt_mohm, scalefactor; |
| |
| rbatt_mohm = chip->batt_data->default_rbatt_mohm; |
| if (chip->batt_data->rbatt_sf_lut == NULL) { |
| pr_debug("RBATT = %d\n", rbatt_mohm); |
| return rbatt_mohm; |
| } |
| |
| scalefactor = interpolate_scalingfactor(chip->batt_data->rbatt_sf_lut, |
| batt_temp, soc); |
| rbatt_mohm = (rbatt_mohm * scalefactor) / 100; |
| |
| if (chip->dt.cfg_r_conn_mohm > 0) |
| rbatt_mohm += chip->dt.cfg_r_conn_mohm; |
| |
| return rbatt_mohm; |
| } |
| |
| static void charging_began(struct qpnp_bms_chip *chip) |
| { |
| int rc; |
| u8 state; |
| |
| mutex_lock(&chip->last_soc_mutex); |
| |
| chip->charge_start_tm_sec = 0; |
| chip->catch_up_time_sec = 0; |
| chip->start_soc = chip->last_soc; |
| |
| /* |
| * reset ocv_at_100 to -EINVAL to indicate |
| * start of charging. |
| */ |
| chip->ocv_at_100 = -EINVAL; |
| |
| mutex_unlock(&chip->last_soc_mutex); |
| |
| /* |
| * If the BMS state is not in S2, force it in S2. Such |
| * a condition can only occur if we are coming out of |
| * suspend. |
| */ |
| mutex_lock(&chip->state_change_mutex); |
| rc = get_fsm_state(chip, &state); |
| if (rc) |
| pr_err("Unable to get FSM state rc=%d\n", rc); |
| if (rc || (state != S2_STATE)) { |
| pr_debug("Forcing S2 state\n"); |
| rc = force_fsm_state(chip, S2_STATE); |
| if (rc) |
| pr_err("Unable to set FSM state rc=%d\n", rc); |
| } |
| mutex_unlock(&chip->state_change_mutex); |
| } |
| |
| static void charging_ended(struct qpnp_bms_chip *chip) |
| { |
| u8 state; |
| int rc, status = get_battery_status(chip); |
| |
| mutex_lock(&chip->last_soc_mutex); |
| |
| chip->charge_start_tm_sec = 0; |
| chip->catch_up_time_sec = 0; |
| chip->end_soc = chip->last_soc; |
| |
| if (status == POWER_SUPPLY_STATUS_FULL) |
| chip->last_soc_invalid = true; |
| |
| mutex_unlock(&chip->last_soc_mutex); |
| |
| /* |
| * If the BMS state is not in S2, force it in S2. Such |
| * a condition can only occur if we are coming out of |
| * suspend. |
| */ |
| mutex_lock(&chip->state_change_mutex); |
| rc = get_fsm_state(chip, &state); |
| if (rc) |
| pr_err("Unable to get FSM state rc=%d\n", rc); |
| if (rc || (state != S2_STATE)) { |
| pr_debug("Forcing S2 state\n"); |
| rc = force_fsm_state(chip, S2_STATE); |
| if (rc) |
| pr_err("Unable to set FSM state rc=%d\n", rc); |
| } |
| mutex_unlock(&chip->state_change_mutex); |
| |
| /* Calculate charge accumulated and update charge cycle */ |
| if (chip->dt.cfg_battery_aging_comp && |
| (chip->end_soc > chip->start_soc)) { |
| chip->charge_increase += (chip->end_soc - chip->start_soc); |
| if (chip->charge_increase > 100) { |
| chip->charge_cycles++; |
| chip->charge_increase %= 100; |
| } |
| pr_debug("start_soc=%u end_soc=%u charge_cycles=%u charge_increase=%u\n", |
| chip->start_soc, chip->end_soc, |
| chip->charge_cycles, chip->charge_increase); |
| rc = backup_charge_cycle(chip); |
| if (rc) |
| pr_err("Unable to store charge cycles rc=%d\n", rc); |
| } |
| } |
| |
| static int estimate_ocv(struct qpnp_bms_chip *chip) |
| { |
| int i, rc, vbatt = 0, vbatt_final = 0; |
| |
| for (i = 0; i < 5; i++) { |
| rc = get_battery_voltage(chip, &vbatt); |
| if (rc) { |
| pr_err("Unable to read battery-voltage rc=%d\n", rc); |
| return rc; |
| } |
| /* |
| * Conservatively select the lowest vbatt to avoid reporting |
| * a higher ocv due to variations in bootup current. |
| */ |
| |
| if (i == 0) |
| vbatt_final = vbatt; |
| else if (vbatt < vbatt_final) |
| vbatt_final = vbatt; |
| |
| msleep(20); |
| } |
| |
| /* |
| * TODO: Revisit the OCV calcuations to use approximate ibatt |
| * and rbatt. |
| */ |
| return vbatt_final; |
| } |
| |
| static int scale_soc_while_chg(struct qpnp_bms_chip *chip, int chg_time_sec, |
| int catch_up_sec, int new_soc, int prev_soc) |
| { |
| int scaled_soc; |
| int numerator; |
| |
| /* |
| * Don't report a high value immediately slowly scale the |
| * value from prev_soc to the new soc based on a charge time |
| * weighted average |
| */ |
| pr_debug("cts=%d catch_up_sec=%d\n", chg_time_sec, catch_up_sec); |
| if (catch_up_sec == 0) |
| return new_soc; |
| |
| if (chg_time_sec > catch_up_sec) |
| return new_soc; |
| |
| numerator = (catch_up_sec - chg_time_sec) * prev_soc |
| + chg_time_sec * new_soc; |
| scaled_soc = numerator / catch_up_sec; |
| |
| pr_debug("cts=%d new_soc=%d prev_soc=%d scaled_soc=%d\n", |
| chg_time_sec, new_soc, prev_soc, scaled_soc); |
| |
| return scaled_soc; |
| } |
| |
| static int report_eoc(struct qpnp_bms_chip *chip) |
| { |
| int rc = -EINVAL; |
| union power_supply_propval ret = {0,}; |
| |
| if (chip->batt_psy == NULL) |
| chip->batt_psy = power_supply_get_by_name("battery"); |
| if (chip->batt_psy) { |
| rc = chip->batt_psy->get_property(chip->batt_psy, |
| POWER_SUPPLY_PROP_STATUS, &ret); |
| if (rc) { |
| pr_err("Unable to get battery 'STATUS' rc=%d\n", rc); |
| } else if (ret.intval != POWER_SUPPLY_STATUS_FULL) { |
| pr_debug("Report EOC to charger\n"); |
| ret.intval = POWER_SUPPLY_STATUS_FULL; |
| rc = chip->batt_psy->set_property(chip->batt_psy, |
| POWER_SUPPLY_PROP_STATUS, &ret); |
| if (rc) { |
| pr_err("Unable to set 'STATUS' rc=%d\n", rc); |
| return rc; |
| } |
| chip->eoc_reported = true; |
| } |
| } else { |
| pr_err("battery psy not registered\n"); |
| } |
| |
| return rc; |
| } |
| |
| static void check_recharge_condition(struct qpnp_bms_chip *chip) |
| { |
| int rc; |
| union power_supply_propval ret = {0,}; |
| int status = get_battery_status(chip); |
| |
| if (chip->last_soc > chip->dt.cfg_soc_resume_limit) |
| return; |
| |
| if (status == POWER_SUPPLY_STATUS_UNKNOWN) { |
| pr_debug("Unable to read battery status\n"); |
| return; |
| } |
| |
| /* Report recharge to charger for SOC based resume of charging */ |
| if ((status != POWER_SUPPLY_STATUS_CHARGING) && chip->eoc_reported) { |
| ret.intval = POWER_SUPPLY_STATUS_CHARGING; |
| rc = chip->batt_psy->set_property(chip->batt_psy, |
| POWER_SUPPLY_PROP_STATUS, &ret); |
| if (rc < 0) { |
| pr_err("Unable to set battery property rc=%d\n", rc); |
| } else { |
| pr_info("soc dropped below resume_soc soc=%d resume_soc=%d, restart charging\n", |
| chip->last_soc, |
| chip->dt.cfg_soc_resume_limit); |
| chip->eoc_reported = false; |
| } |
| } |
| } |
| |
| static void check_eoc_condition(struct qpnp_bms_chip *chip) |
| { |
| int rc; |
| int status = get_battery_status(chip); |
| union power_supply_propval ret = {0,}; |
| |
| if (status == POWER_SUPPLY_STATUS_UNKNOWN) { |
| pr_err("Unable to read battery status\n"); |
| return; |
| } |
| |
| /* |
| * Check battery status: |
| * if last_soc is 100 and battery status is still charging |
| * reset ocv_at_100 and force reporting of eoc to charger. |
| */ |
| if ((chip->last_soc == 100) && |
| (status == POWER_SUPPLY_STATUS_CHARGING)) |
| chip->ocv_at_100 = -EINVAL; |
| |
| /* |
| * Store the OCV value at 100. If the new ocv is greater than |
| * ocv_at_100 (battery settles), update ocv_at_100. Else |
| * if the SOC drops, reset ocv_at_100. |
| */ |
| if (chip->ocv_at_100 == -EINVAL) { |
| if (chip->last_soc == 100) { |
| if (chip->dt.cfg_report_charger_eoc) { |
| rc = report_eoc(chip); |
| if (!rc) { |
| /* |
| * update ocv_at_100 only if EOC is |
| * reported successfully. |
| */ |
| chip->ocv_at_100 = chip->last_ocv_uv; |
| pr_debug("Battery FULL\n"); |
| } else { |
| pr_err("Unable to report eoc rc=%d\n", |
| rc); |
| chip->ocv_at_100 = -EINVAL; |
| } |
| } |
| if (chip->dt.cfg_use_reported_soc) { |
| /* begin reported_soc process */ |
| chip->reported_soc_in_use = true; |
| chip->charger_removed_since_full = false; |
| chip->charger_reinserted = false; |
| chip->reported_soc = 100; |
| pr_debug("Begin reported_soc process\n"); |
| } |
| } |
| } else { |
| if (chip->last_ocv_uv >= chip->ocv_at_100) { |
| pr_debug("new_ocv(%d) > ocv_at_100(%d) maintaining SOC to 100\n", |
| chip->last_ocv_uv, chip->ocv_at_100); |
| chip->ocv_at_100 = chip->last_ocv_uv; |
| chip->last_soc = 100; |
| } else if (chip->last_soc != 100) { |
| /* |
| * Report that the battery is discharging. |
| * This gets called once when the SOC falls |
| * below 100. |
| */ |
| if (chip->reported_soc_in_use |
| && chip->reported_soc == 100) { |
| pr_debug("reported_soc=100, last_soc=%d, do not send DISCHARING status\n", |
| chip->last_soc); |
| } else { |
| ret.intval = POWER_SUPPLY_STATUS_DISCHARGING; |
| chip->batt_psy->set_property(chip->batt_psy, |
| POWER_SUPPLY_PROP_STATUS, &ret); |
| } |
| pr_debug("SOC dropped (%d) discarding ocv_at_100\n", |
| chip->last_soc); |
| chip->ocv_at_100 = -EINVAL; |
| } |
| } |
| } |
| |
| static int report_voltage_based_soc(struct qpnp_bms_chip *chip) |
| { |
| pr_debug("Reported voltage based soc = %d\n", |
| chip->prev_voltage_based_soc); |
| return chip->prev_voltage_based_soc; |
| } |
| |
| static int prepare_reported_soc(struct qpnp_bms_chip *chip) |
| { |
| if (chip->charger_removed_since_full == false) { |
| /* |
| * charger is not removed since full, |
| * keep reported_soc as 100 and calculate the delta soc |
| * between reported_soc and last_soc |
| */ |
| chip->reported_soc = 100; |
| chip->reported_soc_delta = 100 - chip->last_soc; |
| pr_debug("Keep at reported_soc 100, reported_soc_delta=%d, last_soc=%d\n", |
| chip->reported_soc_delta, |
| chip->last_soc); |
| } else { |
| /* charger is removed since full */ |
| if (chip->charger_reinserted) { |
| /* |
| * charger reinserted, keep the reported_soc |
| * until it equals to last_soc. |
| */ |
| if (chip->reported_soc == chip->last_soc) { |
| chip->reported_soc_in_use = false; |
| chip->reported_soc_high_current = false; |
| pr_debug("reported_soc equals to last_soc, stop reported_soc process\n"); |
| } |
| chip->reported_soc_change_sec = 0; |
| } |
| } |
| pr_debug("Reporting reported_soc=%d, last_soc=%d\n", |
| chip->reported_soc, chip->last_soc); |
| return chip->reported_soc; |
| } |
| |
| #define SOC_CATCHUP_SEC_MAX 600 |
| #define SOC_CATCHUP_SEC_PER_PERCENT 60 |
| #define MAX_CATCHUP_SOC (SOC_CATCHUP_SEC_MAX / SOC_CATCHUP_SEC_PER_PERCENT) |
| #define SOC_CHANGE_PER_SEC 5 |
| static int report_vm_bms_soc(struct qpnp_bms_chip *chip) |
| { |
| int soc, soc_change, batt_temp, rc; |
| int time_since_last_change_sec = 0, charge_time_sec = 0; |
| unsigned long last_change_sec; |
| bool charging; |
| |
| soc = chip->calculated_soc; |
| |
| last_change_sec = chip->last_soc_change_sec; |
| calculate_delta_time(&last_change_sec, &time_since_last_change_sec); |
| |
| charging = is_battery_charging(chip); |
| |
| pr_debug("charging=%d last_soc=%d last_soc_unbound=%d\n", |
| charging, chip->last_soc, chip->last_soc_unbound); |
| /* |
| * account for charge time - limit it to SOC_CATCHUP_SEC to |
| * avoid overflows when charging continues for extended periods |
| */ |
| if (charging && chip->last_soc != -EINVAL) { |
| if (chip->charge_start_tm_sec == 0) { |
| /* |
| * calculating soc for the first time |
| * after start of chg. Initialize catchup time |
| */ |
| if (abs(soc - chip->last_soc) < MAX_CATCHUP_SOC) |
| chip->catch_up_time_sec = |
| (soc - chip->last_soc) |
| * SOC_CATCHUP_SEC_PER_PERCENT; |
| else |
| chip->catch_up_time_sec = SOC_CATCHUP_SEC_MAX; |
| |
| if (chip->catch_up_time_sec < 0) |
| chip->catch_up_time_sec = 0; |
| chip->charge_start_tm_sec = last_change_sec; |
| } |
| |
| charge_time_sec = min(SOC_CATCHUP_SEC_MAX, (int)last_change_sec |
| - chip->charge_start_tm_sec); |
| |
| /* end catchup if calculated soc and last soc are same */ |
| if (chip->last_soc == soc) |
| chip->catch_up_time_sec = 0; |
| } |
| |
| if (chip->last_soc != -EINVAL) { |
| /* |
| * last_soc < soc ... if we have not been charging at all |
| * since the last time this was called, report previous SoC. |
| * Otherwise, scale and catch up. |
| */ |
| rc = get_batt_therm(chip, &batt_temp); |
| if (rc) |
| batt_temp = BMS_DEFAULT_TEMP; |
| |
| if (chip->last_soc < soc && !charging) |
| soc = chip->last_soc; |
| else if (chip->last_soc < soc && soc != 100) |
| soc = scale_soc_while_chg(chip, charge_time_sec, |
| chip->catch_up_time_sec, |
| soc, chip->last_soc); |
| |
| /* |
| * if the battery is close to cutoff or if the batt_temp |
| * is under the low-temp threshold allow bigger change |
| */ |
| if (bms_wake_active(&chip->vbms_lv_wake_source) || |
| (batt_temp <= chip->dt.cfg_low_temp_threshold)) |
| soc_change = min((int)abs(chip->last_soc - soc), |
| time_since_last_change_sec); |
| else |
| soc_change = min((int)abs(chip->last_soc - soc), |
| time_since_last_change_sec |
| / SOC_CHANGE_PER_SEC); |
| |
| if (chip->last_soc_unbound) { |
| chip->last_soc_unbound = false; |
| } else { |
| /* |
| * if soc have not been unbound by resume, |
| * only change reported SoC by 1. |
| */ |
| soc_change = min(1, soc_change); |
| } |
| |
| if (soc < chip->last_soc && soc != 0) |
| soc = chip->last_soc - soc_change; |
| if (soc > chip->last_soc && soc != 100) |
| soc = chip->last_soc + soc_change; |
| } |
| |
| if (chip->last_soc != soc && !chip->last_soc_unbound) |
| chip->last_soc_change_sec = last_change_sec; |
| |
| /* |
| * Check/update eoc under following condition: |
| * if there is change in soc: |
| * soc != chip->last_soc |
| * during bootup if soc is 100: |
| */ |
| soc = bound_soc(soc); |
| if ((soc != chip->last_soc) || (soc == 100)) { |
| chip->last_soc = soc; |
| check_eoc_condition(chip); |
| if ((chip->dt.cfg_soc_resume_limit > 0) && !charging) |
| check_recharge_condition(chip); |
| } |
| |
| pr_debug("last_soc=%d calculated_soc=%d soc=%d time_since_last_change=%d\n", |
| chip->last_soc, chip->calculated_soc, |
| soc, time_since_last_change_sec); |
| |
| /* |
| * Backup the actual ocv (last_ocv_uv) and not the |
| * last_soc-interpolated ocv. This makes sure that |
| * the BMS algorithm always uses the correct ocv and |
| * can catch up on the last_soc (across reboots). |
| * We do not want the algorithm to be based of a wrong |
| * initial OCV. |
| */ |
| |
| backup_ocv_soc(chip, chip->last_ocv_uv, chip->last_soc); |
| |
| if (chip->reported_soc_in_use) |
| return prepare_reported_soc(chip); |
| |
| pr_debug("Reported SOC=%d\n", chip->last_soc); |
| |
| return chip->last_soc; |
| } |
| |
| static int report_state_of_charge(struct qpnp_bms_chip *chip) |
| { |
| int soc; |
| |
| mutex_lock(&chip->last_soc_mutex); |
| |
| if (chip->dt.cfg_use_voltage_soc) |
| soc = report_voltage_based_soc(chip); |
| else |
| soc = report_vm_bms_soc(chip); |
| |
| mutex_unlock(&chip->last_soc_mutex); |
| |
| return soc; |
| } |
| |
| static void btm_notify_vbat(enum qpnp_tm_state state, void *ctx) |
| { |
| struct qpnp_bms_chip *chip = ctx; |
| int vbat_uv; |
| int rc; |
| |
| rc = get_battery_voltage(chip, &vbat_uv); |
| if (rc) { |
| pr_err("error reading vbat_sns adc channel=%d, rc=%d\n", |
| VBAT_SNS, rc); |
| goto out; |
| } |
| |
| pr_debug("vbat is at %d, state is at %d\n", vbat_uv, state); |
| |
| if (state == ADC_TM_LOW_STATE) { |
| pr_debug("low voltage btm notification triggered\n"); |
| if (vbat_uv <= (chip->vbat_monitor_params.low_thr |
| + VBATT_ERROR_MARGIN)) { |
| if (!bms_wake_active(&chip->vbms_lv_wake_source)) |
| bms_stay_awake(&chip->vbms_lv_wake_source); |
| |
| chip->vbat_monitor_params.state_request = |
| ADC_TM_HIGH_THR_ENABLE; |
| } else { |
| pr_debug("faulty btm trigger, discarding\n"); |
| goto out; |
| } |
| } else if (state == ADC_TM_HIGH_STATE) { |
| pr_debug("high voltage btm notification triggered\n"); |
| if (vbat_uv > chip->vbat_monitor_params.high_thr) { |
| chip->vbat_monitor_params.state_request = |
| ADC_TM_LOW_THR_ENABLE; |
| if (bms_wake_active(&chip->vbms_lv_wake_source)) |
| bms_relax(&chip->vbms_lv_wake_source); |
| } else { |
| pr_debug("faulty btm trigger, discarding\n"); |
| goto out; |
| } |
| } else { |
| pr_debug("unknown voltage notification state: %d\n", state); |
| goto out; |
| } |
| |
| if (chip->bms_psy_registered) |
| power_supply_changed(&chip->bms_psy); |
| |
| out: |
| qpnp_adc_tm_channel_measure(chip->adc_tm_dev, |
| &chip->vbat_monitor_params); |
| } |
| |
| static int reset_vbat_monitoring(struct qpnp_bms_chip *chip) |
| { |
| int rc; |
| |
| chip->vbat_monitor_params.state_request = ADC_TM_HIGH_LOW_THR_DISABLE; |
| rc = qpnp_adc_tm_channel_measure(chip->adc_tm_dev, |
| &chip->vbat_monitor_params); |
| if (rc) { |
| pr_err("tm disable failed: %d\n", rc); |
| return rc; |
| } |
| |
| if (bms_wake_active(&chip->vbms_lv_wake_source)) |
| bms_relax(&chip->vbms_lv_wake_source); |
| |
| return 0; |
| } |
| |
| static int setup_vbat_monitoring(struct qpnp_bms_chip *chip) |
| { |
| int rc; |
| |
| chip->vbat_monitor_params.low_thr = |
| chip->dt.cfg_low_voltage_threshold; |
| chip->vbat_monitor_params.high_thr = |
| chip->dt.cfg_low_voltage_threshold |
| + VBATT_ERROR_MARGIN; |
| chip->vbat_monitor_params.state_request = ADC_TM_LOW_THR_ENABLE; |
| chip->vbat_monitor_params.channel = VBAT_SNS; |
| chip->vbat_monitor_params.btm_ctx = chip; |
| chip->vbat_monitor_params.timer_interval = ADC_MEAS1_INTERVAL_1S; |
| chip->vbat_monitor_params.threshold_notification = &btm_notify_vbat; |
| pr_debug("set low thr to %d and high to %d\n", |
| chip->vbat_monitor_params.low_thr, |
| chip->vbat_monitor_params.high_thr); |
| |
| rc = qpnp_adc_tm_channel_measure(chip->adc_tm_dev, |
| &chip->vbat_monitor_params); |
| if (rc) { |
| pr_err("adc-tm setup failed: %d\n", rc); |
| return rc; |
| } |
| |
| pr_debug("vbat monitoring setup complete\n"); |
| return 0; |
| } |
| |
| static void very_low_voltage_check(struct qpnp_bms_chip *chip, int vbat_uv) |
| { |
| if (!bms_wake_active(&chip->vbms_lv_wake_source) |
| && (vbat_uv <= chip->dt.cfg_low_voltage_threshold)) { |
| pr_debug("voltage=%d holding low voltage ws\n", vbat_uv); |
| bms_stay_awake(&chip->vbms_lv_wake_source); |
| } else if (bms_wake_active(&chip->vbms_lv_wake_source) |
| && (vbat_uv > chip->dt.cfg_low_voltage_threshold)) { |
| pr_debug("voltage=%d releasing low voltage ws\n", vbat_uv); |
| bms_relax(&chip->vbms_lv_wake_source); |
| } |
| } |
| |
| static void cv_voltage_check(struct qpnp_bms_chip *chip, int vbat_uv) |
| { |
| if (bms_wake_active(&chip->vbms_cv_wake_source)) { |
| if ((vbat_uv < (chip->dt.cfg_max_voltage_uv - |
| VBATT_ERROR_MARGIN + CV_DROP_MARGIN)) |
| && !is_battery_taper_charging(chip)) { |
| pr_debug("Fell below CV, releasing cv ws\n"); |
| chip->in_cv_state = false; |
| bms_relax(&chip->vbms_cv_wake_source); |
| } else if (!is_battery_charging(chip)) { |
| pr_debug("charging stopped, releasing cv ws\n"); |
| chip->in_cv_state = false; |
| bms_relax(&chip->vbms_cv_wake_source); |
| } |
| } else if (!bms_wake_active(&chip->vbms_cv_wake_source) |
| && is_battery_charging(chip) |
| && ((vbat_uv > (chip->dt.cfg_max_voltage_uv - |
| VBATT_ERROR_MARGIN)) |
| || is_battery_taper_charging(chip))) { |
| pr_debug("CC_TO_CV voltage=%d holding cv ws\n", vbat_uv); |
| chip->in_cv_state = true; |
| bms_stay_awake(&chip->vbms_cv_wake_source); |
| } |
| } |
| |
| static void low_soc_check(struct qpnp_bms_chip *chip) |
| { |
| int rc; |
| |
| if (chip->dt.cfg_low_soc_fifo_length < 1) |
| return; |
| |
| mutex_lock(&chip->state_change_mutex); |
| |
| if (chip->calculated_soc <= chip->dt.cfg_low_soc_calc_threshold) { |
| if (!chip->low_soc_fifo_set) { |
| pr_debug("soc=%d (low-soc) setting fifo_length to %d\n", |
| chip->calculated_soc, |
| chip->dt.cfg_low_soc_fifo_length); |
| rc = get_fifo_length(chip, S2_STATE, |
| &chip->s2_fifo_length); |
| if (rc) { |
| pr_err("Unable to get_fifo_length rc=%d", rc); |
| goto low_soc_exit; |
| } |
| rc = set_fifo_length(chip, S2_STATE, |
| chip->dt.cfg_low_soc_fifo_length); |
| if (rc) { |
| pr_err("Unable to set_fifo_length rc=%d", rc); |
| goto low_soc_exit; |
| } |
| chip->low_soc_fifo_set = true; |
| } |
| } else { |
| if (chip->low_soc_fifo_set) { |
| pr_debug("soc=%d setting back fifo_length to %d\n", |
| chip->calculated_soc, |
| chip->s2_fifo_length); |
| rc = set_fifo_length(chip, S2_STATE, |
| chip->s2_fifo_length); |
| if (rc) { |
| pr_err("Unable to set_fifo_length rc=%d", rc); |
| goto low_soc_exit; |
| } |
| chip->low_soc_fifo_set = false; |
| } |
| } |
| |
| low_soc_exit: |
| mutex_unlock(&chip->state_change_mutex); |
| } |
| |
| static int calculate_soc_from_voltage(struct qpnp_bms_chip *chip) |
| { |
| int voltage_range_uv, voltage_remaining_uv, voltage_based_soc; |
| int rc, vbat_uv; |
| |
| /* check if we have the averaged fifo data */ |
| if (chip->voltage_soc_uv) { |
| vbat_uv = chip->voltage_soc_uv; |
| } else { |
| rc = get_battery_voltage(chip, &vbat_uv); |
| if (rc < 0) { |
| pr_err("adc vbat failed err = %d\n", rc); |
| return rc; |
| } |
| pr_debug("instant-voltage based voltage-soc\n"); |
| } |
| |
| voltage_range_uv = chip->dt.cfg_max_voltage_uv - |
| chip->dt.cfg_v_cutoff_uv; |
| voltage_remaining_uv = vbat_uv - chip->dt.cfg_v_cutoff_uv; |
| voltage_based_soc = voltage_remaining_uv * 100 / voltage_range_uv; |
| |
| voltage_based_soc = clamp(voltage_based_soc, 0, 100); |
| |
| if (chip->prev_voltage_based_soc != voltage_based_soc |
| && chip->bms_psy_registered) { |
| pr_debug("update bms_psy\n"); |
| power_supply_changed(&chip->bms_psy); |
| } |
| chip->prev_voltage_based_soc = voltage_based_soc; |
| |
| pr_debug("vbat used = %duv\n", vbat_uv); |
| pr_debug("Calculated voltage based soc=%d\n", voltage_based_soc); |
| |
| if (voltage_based_soc == 100) |
| if (chip->dt.cfg_report_charger_eoc) |
| report_eoc(chip); |
| |
| return 0; |
| } |
| |
| static void calculate_reported_soc(struct qpnp_bms_chip *chip) |
| { |
| union power_supply_propval ret = {0,}; |
| |
| if (chip->reported_soc > chip->last_soc) { |
| /*send DISCHARGING status if the reported_soc drops from 100 */ |
| if (chip->reported_soc == 100) { |
| ret.intval = POWER_SUPPLY_STATUS_DISCHARGING; |
| chip->batt_psy->set_property(chip->batt_psy, |
| POWER_SUPPLY_PROP_STATUS, &ret); |
| pr_debug("Report discharging status, reported_soc=%d, last_soc=%d\n", |
| chip->reported_soc, chip->last_soc); |
| } |
| /* |
| * reported_soc_delta is used to prevent |
| * the big change in last_soc, |
| * this is not used in high current mode |
| */ |
| if (chip->reported_soc_delta > 0) |
| chip->reported_soc_delta--; |
| |
| if (chip->reported_soc_high_current) |
| chip->reported_soc--; |
| else |
| chip->reported_soc = chip->last_soc |
| + chip->reported_soc_delta; |
| |
| pr_debug("New reported_soc=%d, last_soc is=%d\n", |
| chip->reported_soc, chip->last_soc); |
| } else { |
| chip->reported_soc_in_use = false; |
| chip->reported_soc_high_current = false; |
| pr_debug("reported_soc equals last_soc,stop reported_soc process\n"); |
| } |
| pr_debug("bms power_supply_changed\n"); |
| power_supply_changed(&chip->bms_psy); |
| } |
| |
| static int clamp_soc_based_on_voltage(struct qpnp_bms_chip *chip, int soc) |
| { |
| int rc, vbat_uv; |
| |
| rc = get_battery_voltage(chip, &vbat_uv); |
| if (rc < 0) { |
| pr_err("adc vbat failed err = %d\n", rc); |
| return soc; |
| } |
| |
| /* only clamp when discharging */ |
| if (is_battery_charging(chip)) |
| return soc; |
| |
| if (soc <= 0 && vbat_uv > chip->dt.cfg_v_cutoff_uv) { |
| pr_debug("clamping soc to 1, vbat (%d) > cutoff (%d)\n", |
| vbat_uv, chip->dt.cfg_v_cutoff_uv); |
| return 1; |
| } else { |
| pr_debug("not clamping, using soc = %d, vbat = %d and cutoff = %d\n", |
| soc, vbat_uv, chip->dt.cfg_v_cutoff_uv); |
| return soc; |
| } |
| } |
| |
| #define UI_SOC_CATCHUP_TIME (60) |
| static void monitor_soc_work(struct work_struct *work) |
| { |
| struct qpnp_bms_chip *chip = container_of(work, |
| struct qpnp_bms_chip, |
| monitor_soc_work.work); |
| int rc, vbat_uv = 0, new_soc = 0, batt_temp; |
| |
| bms_stay_awake(&chip->vbms_soc_wake_source); |
| |
| calculate_delta_time(&chip->tm_sec, &chip->delta_time_s); |
| pr_debug("elapsed_time=%d\n", chip->delta_time_s); |
| |
| mutex_lock(&chip->last_soc_mutex); |
| |
| if (!is_battery_present(chip)) { |
| /* if battery is not preset report 100% SOC */ |
| pr_debug("battery gone, reporting 100\n"); |
| chip->last_soc_invalid = true; |
| chip->last_soc = -EINVAL; |
| new_soc = 100; |
| } else { |
| rc = get_battery_voltage(chip, &vbat_uv); |
| if (rc < 0) { |
| pr_err("Failed to read battery-voltage rc=%d\n", rc); |
| } else { |
| very_low_voltage_check(chip, vbat_uv); |
| cv_voltage_check(chip, vbat_uv); |
| } |
| |
| if (chip->dt.cfg_use_voltage_soc) { |
| calculate_soc_from_voltage(chip); |
| } else { |
| rc = get_batt_therm(chip, &batt_temp); |
| if (rc < 0) { |
| pr_err("Unable to read batt temp rc=%d, using default=%d\n", |
| rc, BMS_DEFAULT_TEMP); |
| batt_temp = BMS_DEFAULT_TEMP; |
| } |
| |
| if (chip->last_soc_invalid) { |
| chip->last_soc_invalid = false; |
| chip->last_soc = -EINVAL; |
| } |
| new_soc = lookup_soc_ocv(chip, chip->last_ocv_uv, |
| batt_temp); |
| /* clamp soc due to BMS hw/sw immaturities */ |
| new_soc = clamp_soc_based_on_voltage(chip, new_soc); |
| |
| if (chip->calculated_soc != new_soc) { |
| pr_debug("SOC changed! new_soc=%d prev_soc=%d\n", |
| new_soc, chip->calculated_soc); |
| chip->calculated_soc = new_soc; |
| |
| if (chip->calculated_soc == 100) |
| /* update last_soc immediately */ |
| report_vm_bms_soc(chip); |
| |
| pr_debug("update bms_psy\n"); |
| power_supply_changed(&chip->bms_psy); |
| } else if (chip->last_soc != chip->calculated_soc) { |
| pr_debug("update bms_psy\n"); |
| power_supply_changed(&chip->bms_psy); |
| } else { |
| report_vm_bms_soc(chip); |
| } |
| } |
| /* low SOC configuration */ |
| low_soc_check(chip); |
| } |
| /* |
| * schedule the work only if last_soc has not caught up with |
| * the calculated soc or if we are using voltage based soc |
| */ |
| if ((chip->last_soc != chip->calculated_soc) || |
| chip->dt.cfg_use_voltage_soc) |
| schedule_delayed_work(&chip->monitor_soc_work, |
| msecs_to_jiffies(get_calculation_delay_ms(chip))); |
| |
| if (chip->reported_soc_in_use && chip->charger_removed_since_full |
| && !chip->charger_reinserted) { |
| /* record the elapsed time after last reported_soc change */ |
| chip->reported_soc_change_sec += chip->delta_time_s; |
| pr_debug("reported_soc_change_sec=%d\n", |
| chip->reported_soc_change_sec); |
| |
| /* above the catch up time, calculate new reported_soc */ |
| if (chip->reported_soc_change_sec > UI_SOC_CATCHUP_TIME) { |
| calculate_reported_soc(chip); |
| chip->reported_soc_change_sec = 0; |
| } |
| } |
| |
| mutex_unlock(&chip->last_soc_mutex); |
| |
| bms_relax(&chip->vbms_soc_wake_source); |
| } |
| |
| static void voltage_soc_timeout_work(struct work_struct *work) |
| { |
| struct qpnp_bms_chip *chip = container_of(work, |
| struct qpnp_bms_chip, |
| voltage_soc_timeout_work.work); |
| |
| mutex_lock(&chip->bms_device_mutex); |
| if (!chip->bms_dev_open) { |
| pr_warn("BMS device not opened, using voltage based SOC\n"); |
| chip->dt.cfg_use_voltage_soc = true; |
| } |
| mutex_unlock(&chip->bms_device_mutex); |
| } |
| |
| static int get_prop_bms_capacity(struct qpnp_bms_chip *chip) |
| { |
| return report_state_of_charge(chip); |
| } |
| |
| static bool is_hi_power_state_requested(struct qpnp_bms_chip *chip) |
| { |
| |
| pr_debug("hi_power_state=0x%x\n", chip->hi_power_state); |
| |
| if (chip->hi_power_state & VMBMS_IGNORE_ALL_BIT) |
| return false; |
| else |
| return !!chip->hi_power_state; |
| |
| } |
| |
| static int qpnp_vm_bms_config_power_state(struct qpnp_bms_chip *chip, |
| int usecase, bool hi_power_enable) |
| { |
| if (usecase < 0) { |
| pr_err("Invalid power-usecase %x\n", usecase); |
| return -EINVAL; |
| } |
| |
| if (hi_power_enable) |
| chip->hi_power_state |= usecase; |
| else |
| chip->hi_power_state &= ~usecase; |
| |
| pr_debug("hi_power_state=%x usecase=%x hi_power_enable=%d\n", |
| chip->hi_power_state, usecase, hi_power_enable); |
| |
| return 0; |
| } |
| |
| static int get_prop_bms_current_now(struct qpnp_bms_chip *chip) |
| { |
| return chip->current_now; |
| } |
| |
| static enum power_supply_property bms_power_props[] = { |
| POWER_SUPPLY_PROP_CAPACITY, |
| POWER_SUPPLY_PROP_STATUS, |
| POWER_SUPPLY_PROP_RESISTANCE, |
| POWER_SUPPLY_PROP_RESISTANCE_CAPACITIVE, |
| POWER_SUPPLY_PROP_RESISTANCE_NOW, |
| POWER_SUPPLY_PROP_CURRENT_NOW, |
| POWER_SUPPLY_PROP_VOLTAGE_OCV, |
| POWER_SUPPLY_PROP_HI_POWER, |
| POWER_SUPPLY_PROP_LOW_POWER, |
| POWER_SUPPLY_PROP_BATTERY_TYPE, |
| POWER_SUPPLY_PROP_TEMP, |
| POWER_SUPPLY_PROP_CYCLE_COUNT, |
| }; |
| |
| static int |
| qpnp_vm_bms_property_is_writeable(struct power_supply *psy, |
| enum power_supply_property psp) |
| { |
| switch (psp) { |
| case POWER_SUPPLY_PROP_CURRENT_NOW: |
| case POWER_SUPPLY_PROP_VOLTAGE_OCV: |
| case POWER_SUPPLY_PROP_HI_POWER: |
| case POWER_SUPPLY_PROP_LOW_POWER: |
| return 1; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int qpnp_vm_bms_power_get_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| union power_supply_propval *val) |
| { |
| struct qpnp_bms_chip *chip = container_of(psy, |
| struct qpnp_bms_chip, bms_psy); |
| int value = 0, rc; |
| |
| val->intval = 0; |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_CAPACITY: |
| val->intval = get_prop_bms_capacity(chip); |
| break; |
| case POWER_SUPPLY_PROP_STATUS: |
| val->intval = chip->battery_status; |
| break; |
| case POWER_SUPPLY_PROP_RESISTANCE: |
| val->intval = get_prop_bms_rbatt(chip); |
| break; |
| case POWER_SUPPLY_PROP_RESISTANCE_CAPACITIVE: |
| if (chip->batt_data->rbatt_capacitive_mohm > 0) |
| val->intval = chip->batt_data->rbatt_capacitive_mohm; |
| if (chip->dt.cfg_r_conn_mohm > 0) |
| val->intval += chip->dt.cfg_r_conn_mohm; |
| break; |
| case POWER_SUPPLY_PROP_RESISTANCE_NOW: |
| rc = get_batt_therm(chip, &value); |
| if (rc < 0) |
| value = BMS_DEFAULT_TEMP; |
| val->intval = get_rbatt(chip, chip->calculated_soc, value); |
| break; |
| case POWER_SUPPLY_PROP_CURRENT_NOW: |
| val->intval = get_prop_bms_current_now(chip); |
| break; |
| case POWER_SUPPLY_PROP_BATTERY_TYPE: |
| val->strval = chip->batt_data->battery_type; |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_OCV: |
| val->intval = chip->last_ocv_uv; |
| break; |
| case POWER_SUPPLY_PROP_TEMP: |
| rc = get_batt_therm(chip, &value); |
| if (rc < 0) |
| value = BMS_DEFAULT_TEMP; |
| val->intval = value; |
| break; |
| case POWER_SUPPLY_PROP_HI_POWER: |
| val->intval = is_hi_power_state_requested(chip); |
| break; |
| case POWER_SUPPLY_PROP_LOW_POWER: |
| val->intval = !is_hi_power_state_requested(chip); |
| break; |
| case POWER_SUPPLY_PROP_CYCLE_COUNT: |
| if (chip->dt.cfg_battery_aging_comp) |
| val->intval = chip->charge_cycles; |
| else |
| val->intval = -EINVAL; |
| break; |
| default: |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static int qpnp_vm_bms_power_set_property(struct power_supply *psy, |
| enum power_supply_property psp, |
| const union power_supply_propval *val) |
| { |
| int rc = 0; |
| struct qpnp_bms_chip *chip = container_of(psy, |
| struct qpnp_bms_chip, bms_psy); |
| |
| switch (psp) { |
| case POWER_SUPPLY_PROP_CURRENT_NOW: |
| chip->current_now = val->intval; |
| pr_debug("IBATT = %d\n", val->intval); |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_OCV: |
| cancel_delayed_work_sync(&chip->monitor_soc_work); |
| chip->last_ocv_uv = val->intval; |
| pr_debug("OCV = %d\n", val->intval); |
| schedule_delayed_work(&chip->monitor_soc_work, 0); |
| break; |
| case POWER_SUPPLY_PROP_HI_POWER: |
| rc = qpnp_vm_bms_config_power_state(chip, val->intval, true); |
| if (rc) |
| pr_err("Unable to set power-state rc=%d\n", rc); |
| break; |
| case POWER_SUPPLY_PROP_LOW_POWER: |
| rc = qpnp_vm_bms_config_power_state(chip, val->intval, false); |
| if (rc) |
| pr_err("Unable to set power-state rc=%d\n", rc); |
| break; |
| default: |
| return -EINVAL; |
| } |
| return rc; |
| } |
| |
| static void bms_new_battery_setup(struct qpnp_bms_chip *chip) |
| { |
| int rc; |
| |
| mutex_lock(&chip->bms_data_mutex); |
| |
| chip->last_soc_invalid = true; |
| /* |
| * disable and re-enable the BMS hardware to reset |
| * the realtime-FIFO data and restart accumulation |
| */ |
| rc = qpnp_masked_write_base(chip, chip->base + EN_CTL_REG, |
| BMS_EN_BIT, 0); |
| /* delay for the BMS hardware to reset its state */ |
| msleep(200); |
| rc |= qpnp_masked_write_base(chip, chip->base + EN_CTL_REG, |
| BMS_EN_BIT, BMS_EN_BIT); |
| /* delay for the BMS hardware to re-start */ |
| msleep(200); |
| if (rc) |
| pr_err("Unable to reset BMS rc=%d\n", rc); |
| |
| chip->last_ocv_uv = estimate_ocv(chip); |
| |
| memset(&chip->bms_data, 0, sizeof(chip->bms_data)); |
| |
| /* update the sequence number */ |
| chip->bms_data.seq_num = chip->seq_num++; |
| |
| /* signal the read thread */ |
| chip->data_ready = 1; |
| wake_up_interruptible(&chip->bms_wait_q); |
| |
| /* hold a wake lock until the read thread is scheduled */ |
| if (chip->bms_dev_open) |
| pm_stay_awake(chip->dev); |
| |
| mutex_unlock(&chip->bms_data_mutex); |
| |
| /* reset aging variables */ |
| if (chip->dt.cfg_battery_aging_comp) { |
| chip->charge_cycles = 0; |
| chip->charge_increase = 0; |
| rc = backup_charge_cycle(chip); |
| if (rc) |
| pr_err("Unable to reset aging data rc=%d\n", rc); |
| } |
| } |
| |
| static void battery_insertion_check(struct qpnp_bms_chip *chip) |
| { |
| int present = (int)is_battery_present(chip); |
| |
| if (chip->battery_present != present) { |
| pr_debug("shadow_sts=%d status=%d\n", |
| chip->battery_present, present); |
| if (chip->battery_present != -EINVAL) { |
| if (present) { |
| /* new battery inserted */ |
| bms_new_battery_setup(chip); |
| setup_vbat_monitoring(chip); |
| pr_debug("New battery inserted!\n"); |
| } else { |
| /* battery removed */ |
| reset_vbat_monitoring(chip); |
| pr_debug("Battery removed\n"); |
| } |
| } |
| chip->battery_present = present; |
| } |
| } |
| |
| static void battery_status_check(struct qpnp_bms_chip *chip) |
| { |
| int status = get_battery_status(chip); |
| |
| if (chip->battery_status != status) { |
| if (status == POWER_SUPPLY_STATUS_CHARGING) { |
| pr_debug("charging started\n"); |
| charging_began(chip); |
| } else if (chip->battery_status == |
| POWER_SUPPLY_STATUS_CHARGING) { |
| pr_debug("charging stopped\n"); |
| charging_ended(chip); |
| } |
| |
| if (status == POWER_SUPPLY_STATUS_FULL) { |
| pr_debug("battery full\n"); |
| chip->battery_full = true; |
| } else if (chip->battery_status == POWER_SUPPLY_STATUS_FULL) { |
| pr_debug("battery not-full anymore\n"); |
| chip->battery_full = false; |
| } |
| chip->battery_status = status; |
| } |
| } |
| |
| #define HIGH_CURRENT_TH 2 |
| static void reported_soc_check_status(struct qpnp_bms_chip *chip) |
| { |
| u8 present; |
| |
| present = is_charger_present(chip); |
| pr_debug("usb_present=%d\n", present); |
| |
| if (!present && !chip->charger_removed_since_full) { |
| chip->charger_removed_since_full = true; |
| pr_debug("reported_soc: charger removed since full\n"); |
| return; |
| } |
| if (chip->reported_soc_high_current) { |
| pr_debug("reported_soc in high current mode, return\n"); |
| return; |
| } |
| if ((chip->reported_soc - chip->last_soc) > |
| (100 - chip->dt.cfg_soc_resume_limit |
| + HIGH_CURRENT_TH)) { |
| chip->reported_soc_high_current = true; |
| chip->charger_removed_since_full = true; |
| chip->charger_reinserted = false; |
| pr_debug("reported_soc enters high current mode\n"); |
| return; |
| } |
| if (present && chip->charger_removed_since_full) { |
| chip->charger_reinserted = true; |
| pr_debug("reported_soc: charger reinserted\n"); |
| } |
| if (!present && chip->charger_removed_since_full) { |
| chip->charger_reinserted = false; |
| pr_debug("reported_soc: charger removed again\n"); |
| } |
| } |
| |
| static void qpnp_vm_bms_ext_power_changed(struct power_supply *psy) |
| { |
| struct qpnp_bms_chip *chip = container_of(psy, struct qpnp_bms_chip, |
| bms_psy); |
| |
| pr_debug("Triggered!\n"); |
| battery_status_check(chip); |
| battery_insertion_check(chip); |
| |
| if (chip->reported_soc_in_use) |
| reported_soc_check_status(chip); |
| } |
| |
| |
| static void dump_bms_data(const char *func, struct qpnp_bms_chip *chip) |
| { |
| int i; |
| |
| pr_debug("%s: fifo_count=%d acc_count=%d seq_num=%d\n", |
| func, chip->bms_data.num_fifo, |
| chip->bms_data.acc_count, |
| chip->bms_data.seq_num); |
| |
| for (i = 0; i < chip->bms_data.num_fifo; i++) |
| pr_debug("fifo=%d fifo_uv=%d sample_interval=%d sample_count=%d\n", |
| i, chip->bms_data.fifo_uv[i], |
| chip->bms_data.sample_interval_ms, |
| chip->bms_data.sample_count); |
| pr_debug("avg_acc_data=%d\n", chip->bms_data.acc_uv); |
| } |
| |
| static int read_and_populate_fifo_data(struct qpnp_bms_chip *chip) |
| { |
| u8 fifo_count = 0, val = 0; |
| u8 fifo_data_raw[MAX_FIFO_REGS * 2]; |
| u16 fifo_data; |
| int rc, i, j; |
| int64_t voltage_soc_avg = 0; |
| |
| /* read the completed FIFO count */ |
| rc = qpnp_read_wrapper(chip, &val, chip->base + STATUS2_REG, 1); |
| if (rc) { |
| pr_err("Unable to read STATUS2 register rc=%d\n", rc); |
| return rc; |
| } |
| fifo_count = (val & FIFO_CNT_SD_MASK) >> FIFO_CNT_SD_SHIFT; |
| pr_debug("fifo_count=%d\n", fifo_count); |
| if (!fifo_count) { |
| pr_debug("No data in FIFO\n"); |
| return 0; |
| } else if (fifo_count > MAX_FIFO_REGS) { |
| pr_err("Invalid fifo-length %d rejecting data\n", fifo_count); |
| chip->bms_data.num_fifo = 0; |
| return 0; |
| } |
| |
| /* read the FIFO data */ |
| for (i = 0; i < fifo_count * 2; i++) { |
| rc = qpnp_read_wrapper(chip, &fifo_data_raw[i], |
| chip->base + FIFO_0_LSB_REG + i, 1); |
| if (rc) { |
| pr_err("Unable to read FIFO register(%d) rc=%d\n", |
| i, rc); |
| return rc; |
| } |
| } |
| |
| /* populate the structure */ |
| chip->bms_data.num_fifo = fifo_count; |
| |
| rc = get_sample_interval(chip, chip->current_fsm_state, |
| &chip->bms_data.sample_interval_ms); |
| if (rc) { |
| pr_err("Unable to read state=%d sample_interval rc=%d\n", |
| chip->current_fsm_state, rc); |
| return rc; |
| } |
| |
| rc = get_sample_count(chip, chip->current_fsm_state, |
| &chip->bms_data.sample_count); |
| if (rc) { |
| pr_err("Unable to read state=%d sample_count rc=%d\n", |
| chip->current_fsm_state, rc); |
| return rc; |
| } |
| |
| for (i = 0, j = 0; i < fifo_count * 2; i = i + 2, j++) { |
| fifo_data = fifo_data_raw[i] | (fifo_data_raw[i + 1] << 8); |
| chip->bms_data.fifo_uv[j] = convert_vbatt_raw_to_uv(chip, |
| fifo_data, 0); |
| voltage_soc_avg += chip->bms_data.fifo_uv[j]; |
| } |
| /* store the fifo average for voltage-based-soc */ |
| chip->voltage_soc_uv = div_u64(voltage_soc_avg, fifo_count); |
| |
| return 0; |
| } |
| |
| static int read_and_populate_acc_data(struct qpnp_bms_chip *chip) |
| { |
| int rc; |
| u32 acc_data_sd = 0, acc_count_sd = 0, avg_acc_data = 0; |
| |
| /* read ACC SD count */ |
| rc = qpnp_read_wrapper(chip, (u8 *)&acc_count_sd, |
| chip->base + ACC_CNT_SD_REG, 1); |
| if (rc) { |
| pr_err("Unable to read ACC_CNT_SD_REG rc=%d\n", rc); |
| return rc; |
| } |
| if (!acc_count_sd) { |
| pr_debug("No data in accumulator\n"); |
| return 0; |
| } |
| /* read ACC SD data */ |
| rc = qpnp_read_wrapper(chip, (u8 *)&acc_data_sd, |
| chip->base + ACC_DATA0_SD_REG, 3); |
| if (rc) { |
| pr_err("Unable to read ACC_DATA0_SD_REG rc=%d\n", rc); |
| return rc; |
| } |
| avg_acc_data = div_u64(acc_data_sd, acc_count_sd); |
| |
| chip->bms_data.acc_uv = convert_vbatt_raw_to_uv(chip, |
| avg_acc_data, 0); |
| chip->bms_data.acc_count = acc_count_sd; |
| |
| rc = get_sample_interval(chip, chip->current_fsm_state, |
| &chip->bms_data.sample_interval_ms); |
| if (rc) { |
| pr_err("Unable to read state=%d sample_interval rc=%d\n", |
| chip->current_fsm_state, rc); |
| return rc; |
| } |
| |
| rc = get_sample_count(chip, chip->current_fsm_state, |
| &chip->bms_data.sample_count); |
| if (rc) { |
| pr_err("Unable to read state=%d sample_count rc=%d\n", |
| chip->current_fsm_state, rc); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static int clear_fifo_acc_data(struct qpnp_bms_chip *chip) |
| { |
| int rc; |
| u8 reg = 0; |
| |
| reg = FIFO_CNT_SD_CLR_BIT | ACC_DATA_SD_CLR_BIT | ACC_CNT_SD_CLR_BIT; |
| rc = qpnp_masked_write_base(chip, chip->base + DATA_CTL2_REG, reg, reg); |
| if (rc) |
| pr_err("Unable to write DATA_CTL2_REG rc=%d\n", rc); |
| |
| return rc; |
| } |
| |
| static irqreturn_t bms_fifo_update_done_irq_handler(int irq, void *_chip) |
| { |
| int rc; |
| struct qpnp_bms_chip *chip = _chip; |
| |
| pr_debug("fifo_update_done triggered\n"); |
| |
| mutex_lock(&chip->bms_data_mutex); |
| |
| if (chip->suspend_data_valid) { |
| pr_debug("Suspend data not processed yet\n"); |
| goto fail_fifo; |
| } |
| |
| rc = calib_vadc(chip); |
| if (rc) |
| pr_err("Unable to calibrate vadc rc=%d\n", rc); |
| |
| /* clear old data */ |
| memset(&chip->bms_data, 0, sizeof(chip->bms_data)); |
| /* |
| * 1. Read FIFO and populate the bms_data |
| * 2. Clear FIFO data |
| * 3. Notify userspace |
| */ |
| rc = update_fsm_state(chip); |
| if (rc) { |
| pr_err("Unable to read FSM state rc=%d\n", rc); |
| goto fail_fifo; |
| } |
| pr_debug("fsm_state=%d\n", chip->current_fsm_state); |
| |
| rc = read_and_populate_fifo_data(chip); |
| if (rc) { |
| pr_err("Unable to read FIFO data rc=%d\n", rc); |
| goto fail_fifo; |
| } |
| |
| rc = clear_fifo_acc_data(chip); |
| if (rc) |
| pr_err("Unable to clear FIFO/ACC data rc=%d\n", rc); |
| |
| /* update the sequence number */ |
| chip->bms_data.seq_num = chip->seq_num++; |
| |
| dump_bms_data(__func__, chip); |
| |
| /* signal the read thread */ |
| chip->data_ready = 1; |
| wake_up_interruptible(&chip->bms_wait_q); |
| |
| /* hold a wake lock until the read thread is scheduled */ |
| if (chip->bms_dev_open) |
| pm_stay_awake(chip->dev); |
| fail_fifo: |
| mutex_unlock(&chip->bms_data_mutex); |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t bms_fsm_state_change_irq_handler(int irq, void *_chip) |
| { |
| int rc; |
| struct qpnp_bms_chip *chip = _chip; |
| |
| pr_debug("fsm_state_changed triggered\n"); |
| |
| mutex_lock(&chip->bms_data_mutex); |
| |
| if (chip->suspend_data_valid) { |
| pr_debug("Suspend data not processed yet\n"); |
| goto fail_state; |
| } |
| |
| rc = calib_vadc(chip); |
| if (rc) |
| pr_err("Unable to calibrate vadc rc=%d\n", rc); |
| |
| /* clear old data */ |
| memset(&chip->bms_data, 0, sizeof(chip->bms_data)); |
| /* |
| * 1. Read FIFO and ACC_DATA and populate the bms_data |
| * 2. Clear FIFO & ACC data |
| * 3. Notify userspace |
| */ |
| pr_debug("prev_fsm_state=%d\n", chip->current_fsm_state); |
| |
| rc = read_and_populate_fifo_data(chip); |
| if (rc) { |
| pr_err("Unable to read FIFO data rc=%d\n", rc); |
| goto fail_state; |
| } |
| |
| /* read accumulator data */ |
| rc = read_and_populate_acc_data(chip); |
| if (rc) { |
| pr_err("Unable to read ACC_SD data rc=%d\n", rc); |
| goto fail_state; |
| } |
| |
| rc = update_fsm_state(chip); |
| if (rc) { |
| pr_err("Unable to read FSM state rc=%d\n", rc); |
| goto fail_state; |
| } |
| |
| rc = clear_fifo_acc_data(chip); |
| if (rc) |
| pr_err("Unable to clear FIFO/ACC data rc=%d\n", rc); |
| |
| /* update the sequence number */ |
| chip->bms_data.seq_num = chip->seq_num++; |
| |
| dump_bms_data(__func__, chip); |
| |
| /* signal the read thread */ |
| chip->data_ready = 1; |
| wake_up_interruptible(&chip->bms_wait_q); |
| |
| /* hold a wake lock until the read thread is scheduled */ |
| if (chip->bms_dev_open) |
| pm_stay_awake(chip->dev); |
| fail_state: |
| mutex_unlock(&chip->bms_data_mutex); |
| return IRQ_HANDLED; |
| } |
| |
| static int read_shutdown_ocv_soc(struct qpnp_bms_chip *chip) |
| { |
| u8 stored_soc = 0; |
| u16 stored_ocv = 0; |
| int rc; |
| |
| rc = qpnp_read_wrapper(chip, (u8 *)&stored_ocv, |
| chip->base + BMS_OCV_REG, 2); |
| if (rc) { |
| pr_err("failed to read addr = %d %d\n", |
| chip->base + BMS_OCV_REG, rc); |
| return -EINVAL; |
| } |
| |
| /* if shutdwon ocv is invalid, reject shutdown soc too */ |
| if (!stored_ocv || (stored_ocv == OCV_INVALID)) { |
| pr_debug("shutdown OCV %d - invalid\n", stored_ocv); |
| chip->shutdown_ocv = OCV_INVALID; |
| chip->shutdown_soc = SOC_INVALID; |
| return -EINVAL; |
| } |
| chip->shutdown_ocv = stored_ocv * 1000; |
| |
| /* |
| * The previous SOC is stored in the first 7 bits of the register as |
| * (Shutdown SOC + 1). This allows for register reset values of both |
| * 0x00 and 0xFF. |
| */ |
| rc = qpnp_read_wrapper(chip, &stored_soc, chip->base + BMS_SOC_REG, 1); |
| if (rc) { |
| pr_err("failed to read addr = %d %d\n", |
| chip->base + BMS_SOC_REG, rc); |
| return -EINVAL; |
| } |
| |
| if (!stored_soc || stored_soc == SOC_INVALID) { |
| chip->shutdown_soc = SOC_INVALID; |
| chip->shutdown_ocv = OCV_INVALID; |
| return -EINVAL; |
| } else { |
| chip->shutdown_soc = (stored_soc >> 1) - 1; |
| } |
| |
| pr_debug("shutdown_ocv=%d shutdown_soc=%d\n", |
| chip->shutdown_ocv, chip->shutdown_soc); |
| |
| return 0; |
| } |
| |
| static int interpolate_current_comp(int die_temp) |
| { |
| int i; |
| int num_rows = ARRAY_SIZE(temp_curr_comp_lut); |
| |
| if (die_temp <= (temp_curr_comp_lut[0].temp_decideg)) |
| return temp_curr_comp_lut[0].current_ma; |
| |
| if (die_temp >= (temp_curr_comp_lut[num_rows - 1].temp_decideg)) |
| return temp_curr_comp_lut[num_rows - 1].current_ma; |
| |
| for (i = 0; i < num_rows - 1; i++) |
| if (die_temp <= (temp_curr_comp_lut[i].temp_decideg)) |
| break; |
| |
| if (die_temp == (temp_curr_comp_lut[i].temp_decideg)) |
| return temp_curr_comp_lut[i].current_ma; |
| |
| return linear_interpolate( |
| temp_curr_comp_lut[i - 1].current_ma, |
| temp_curr_comp_lut[i - 1].temp_decideg, |
| temp_curr_comp_lut[i].current_ma, |
| temp_curr_comp_lut[i].temp_decideg, |
| die_temp); |
| } |
| |
| static void adjust_pon_ocv(struct qpnp_bms_chip *chip, int batt_temp) |
| { |
| int rc, current_ma, rbatt_mohm, die_temp, delta_uv, pc; |
| struct qpnp_vadc_result result; |
| |
| rc = qpnp_vadc_read(chip->vadc_dev, DIE_TEMP, &result); |
| if (rc) { |
| pr_err("error reading adc channel=%d, rc=%d\n", DIE_TEMP, rc); |
| } else { |
| pc = interpolate_pc(chip->batt_data->pc_temp_ocv_lut, |
| batt_temp, chip->last_ocv_uv / 1000); |
| /* |
| * For pc < 2, use the rbatt of pc = 2. This is to avoid |
| * the huge rbatt values at pc < 2 which can disrupt the pon_ocv |
| * calculations. |
| */ |
| if (pc < 2) |
| pc = 2; |
| rbatt_mohm = get_rbatt(chip, pc, batt_temp); |
| /* convert die_temp to DECIDEGC */ |
| die_temp = (int)result.physical / 100; |
| current_ma = interpolate_current_comp(die_temp); |
| delta_uv = rbatt_mohm * current_ma; |
| pr_debug("PON OCV changed from %d to %d pc=%d rbatt=%d current_ma=%d die_temp=%d batt_temp=%d delta_uv=%d\n", |
| chip->last_ocv_uv, chip->last_ocv_uv + delta_uv, pc, |
| rbatt_mohm, current_ma, die_temp, batt_temp, delta_uv); |
| |
| chip->last_ocv_uv += delta_uv; |
| } |
| } |
| |
| static int calculate_initial_soc(struct qpnp_bms_chip *chip) |
| { |
| int rc, batt_temp = 0, est_ocv = 0; |
| |
| rc = get_batt_therm(chip, &batt_temp); |
| if (rc < 0) { |
| pr_err("Unable to read batt temp, using default=%d\n", |
| BMS_DEFAULT_TEMP); |
| batt_temp = BMS_DEFAULT_TEMP; |
| } |
| |
| rc = read_and_update_ocv(chip, batt_temp, true); |
| if (rc) { |
| pr_err("Unable to read PON OCV rc=%d\n", rc); |
| return rc; |
| } |
| |
| rc = read_shutdown_ocv_soc(chip); |
| if (rc < 0 || chip->dt.cfg_ignore_shutdown_soc) |
| chip->shutdown_soc_invalid = true; |
| |
| if (chip->warm_reset) { |
| /* |
| * if we have powered on from warm reset - |
| * Always use shutdown SOC. If shudown SOC is invalid then |
| * estimate OCV |
| */ |
| if (chip->shutdown_soc_invalid) { |
| pr_debug("Estimate OCV\n"); |
| est_ocv = estimate_ocv(chip); |
| if (est_ocv <= 0) { |
| pr_err("Unable to estimate OCV rc=%d\n", |
| est_ocv); |
| return -EINVAL; |
| } |
| chip->last_ocv_uv = est_ocv; |
| chip->calculated_soc = lookup_soc_ocv(chip, est_ocv, |
| batt_temp); |
| } else { |
| chip->last_ocv_uv = chip->shutdown_ocv; |
| chip->last_soc = chip->shutdown_soc; |
| chip->calculated_soc = lookup_soc_ocv(chip, |
| chip->shutdown_ocv, batt_temp); |
| pr_debug("Using shutdown SOC\n"); |
| } |
| } else { |
| /* |
| * In PM8916 2.0 PON OCV calculation is delayed due to |
| * change in the ordering of power-on sequence of LDO6. |
| * Adjust PON OCV to include current during PON. |
| */ |
| if (chip->workaround_flag & WRKARND_PON_OCV_COMP) |
| adjust_pon_ocv(chip, batt_temp); |
| |
| /* !warm_reset use PON OCV only if shutdown SOC is invalid */ |
| chip->calculated_soc = lookup_soc_ocv(chip, |
| chip->last_ocv_uv, batt_temp); |
| if (!chip->shutdown_soc_invalid && |
| (abs(chip->shutdown_soc - chip->calculated_soc) < |
| chip->dt.cfg_shutdown_soc_valid_limit)) { |
| chip->last_ocv_uv = chip->shutdown_ocv; |
| chip->last_soc = chip->shutdown_soc; |
| chip->calculated_soc = lookup_soc_ocv(chip, |
| chip->shutdown_ocv, batt_temp); |
| pr_debug("Using shutdown SOC\n"); |
| } else { |
| chip->shutdown_soc_invalid = true; |
| pr_debug("Using PON SOC\n"); |
| } |
| } |
| /* store the start-up OCV for voltage-based-soc */ |
| chip->voltage_soc_uv = chip->last_ocv_uv; |
| |
| pr_info("warm_reset=%d est_ocv=%d shutdown_soc_invalid=%d shutdown_ocv=%d shutdown_soc=%d last_soc=%d calculated_soc=%d last_ocv_uv=%d\n", |
| chip->warm_reset, est_ocv, chip->shutdown_soc_invalid, |
| chip->shutdown_ocv, chip->shutdown_soc, chip->last_soc, |
| chip->calculated_soc, chip->last_ocv_uv); |
| |
| return 0; |
| } |
| |
| static int calculate_initial_aging_comp(struct qpnp_bms_chip *chip) |
| { |
| int rc; |
| bool battery_removed = is_battery_replaced_in_offmode(chip); |
| |
| if (battery_removed || chip->shutdown_soc_invalid) { |
| pr_info("Clearing aging data battery_removed=%d shutdown_soc_invalid=%d\n", |
| battery_removed, chip->shutdown_soc_invalid); |
| chip->charge_cycles = 0; |
| chip->charge_increase = 0; |
| rc = backup_charge_cycle(chip); |
| if (rc) |
| pr_err("Unable to reset aging data rc=%d\n", rc); |
| } else { |
| rc = read_chgcycle_data_from_backup(chip); |
| if (rc) |
| pr_err("Unable to read aging data rc=%d\n", rc); |
| } |
| |
| pr_debug("Initial aging data charge_cycles=%u charge_increase=%u\n", |
| chip->charge_cycles, chip->charge_increase); |
| return rc; |
| } |
| |
| static int bms_load_hw_defaults(struct qpnp_bms_chip *chip) |
| { |
| u8 val, state, bms_en = 0; |
| u32 interval[2], count[2], fifo[2]; |
| int rc; |
| |
| /* S3 OCV tolerence threshold */ |
| if (chip->dt.cfg_s3_ocv_tol_uv >= 0 && |
| chip->dt.cfg_s3_ocv_tol_uv <= MAX_OCV_TOL_THRESHOLD) { |
| val = chip->dt.cfg_s3_ocv_tol_uv / OCV_TOL_LSB_UV; |
| rc = qpnp_masked_write_base(chip, |
| chip->base + S3_OCV_TOL_CTL_REG, 0xFF, val); |
| if (rc) { |
| pr_err("Unable to write s3_ocv_tol_threshold rc=%d\n", |
| rc); |
| return rc; |
| } |
| } |
| |
| /* S1 accumulator threshold */ |
| if (chip->dt.cfg_s1_sample_count >= 1 && |
| chip->dt.cfg_s1_sample_count <= MAX_SAMPLE_COUNT) { |
| val = (chip->dt.cfg_s1_sample_count > 1) ? |
| (ilog2(chip->dt.cfg_s1_sample_count) - 1) : 0; |
| rc = qpnp_masked_write_base(chip, |
| chip->base + S1_ACC_CNT_REG, |
| ACC_CNT_MASK, val); |
| if (rc) { |
| pr_err("Unable to write s1 sample count rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| /* S2 accumulator threshold */ |
| if (chip->dt.cfg_s2_sample_count >= 1 && |
| chip->dt.cfg_s2_sample_count <= MAX_SAMPLE_COUNT) { |
| val = (chip->dt.cfg_s2_sample_count > 1) ? |
| (ilog2(chip->dt.cfg_s2_sample_count) - 1) : 0; |
| rc = qpnp_masked_write_base(chip, |
| chip->base + S2_ACC_CNT_REG, |
| ACC_CNT_MASK, val); |
| if (rc) { |
| pr_err("Unable to write s2 sample count rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| if (chip->dt.cfg_s1_sample_interval_ms >= 0 && |
| chip->dt.cfg_s1_sample_interval_ms <= MAX_SAMPLE_INTERVAL) { |
| val = chip->dt.cfg_s1_sample_interval_ms / 10; |
| rc = qpnp_write_wrapper(chip, &val, |
| chip->base + S1_SAMPLE_INTVL_REG, 1); |
| if (rc) { |
| pr_err("Unable to write s1 sample inteval rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| if (chip->dt.cfg_s2_sample_interval_ms >= 0 && |
| chip->dt.cfg_s2_sample_interval_ms <= MAX_SAMPLE_INTERVAL) { |
| val = chip->dt.cfg_s2_sample_interval_ms / 10; |
| rc = qpnp_write_wrapper(chip, &val, |
| chip->base + S2_SAMPLE_INTVL_REG, 1); |
| if (rc) { |
| pr_err("Unable to write s2 sample inteval rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| if (chip->dt.cfg_s1_fifo_length >= 0 && |
| chip->dt.cfg_s1_fifo_length <= MAX_FIFO_REGS) { |
| rc = qpnp_masked_write_base(chip, chip->base + FIFO_LENGTH_REG, |
| S1_FIFO_LENGTH_MASK, |
| chip->dt.cfg_s1_fifo_length); |
| if (rc) { |
| pr_err("Unable to write s1 fifo length rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| if (chip->dt.cfg_s2_fifo_length >= 0 && |
| chip->dt.cfg_s2_fifo_length <= MAX_FIFO_REGS) { |
| rc = qpnp_masked_write_base(chip, chip->base + |
| FIFO_LENGTH_REG, S2_FIFO_LENGTH_MASK, |
| chip->dt.cfg_s2_fifo_length |
| << S2_FIFO_LENGTH_SHIFT); |
| if (rc) { |
| pr_err("Unable to write s2 fifo length rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| get_sample_interval(chip, S1_STATE, &interval[0]); |
| get_sample_interval(chip, S2_STATE, &interval[1]); |
| get_sample_count(chip, S1_STATE, &count[0]); |
| get_sample_count(chip, S2_STATE, &count[1]); |
| get_fifo_length(chip, S1_STATE, &fifo[0]); |
| get_fifo_length(chip, S2_STATE, &fifo[1]); |
| |
| /* Force the BMS state to S2 at boot-up */ |
| rc = get_fsm_state(chip, &state); |
| if (rc) |
| pr_err("Unable to get FSM state rc=%d\n", rc); |
| if (rc || (state != S2_STATE)) { |
| pr_debug("Forcing S2 state\n"); |
| rc = force_fsm_state(chip, S2_STATE); |
| if (rc) |
| pr_err("Unable to set FSM state rc=%d\n", rc); |
| } |
| |
| rc = qpnp_read_wrapper(chip, &bms_en, chip->base + EN_CTL_REG, 1); |
| if (rc) { |
| pr_err("Unable to read BMS_EN state rc=%d\n", rc); |
| return rc; |
| } |
| |
| rc = update_fsm_state(chip); |
| if (rc) { |
| pr_err("Unable to read FSM state rc=%d\n", rc); |
| return rc; |
| } |
| |
| pr_info("BMS_EN=%d Sample_Interval-S1=[%d]S2=[%d] Sample_Count-S1=[%d]S2=[%d] Fifo_Length-S1=[%d]S2=[%d] FSM_state=%d\n", |
| !!bms_en, interval[0], interval[1], count[0], |
| count[1], fifo[0], fifo[1], |
| chip->current_fsm_state); |
| |
| return 0; |
| } |
| |
| static ssize_t vm_bms_read(struct file *file, char __user *buf, size_t count, |
| loff_t *ppos) |
| { |
| int rc; |
| struct qpnp_bms_chip *chip = file->private_data; |
| |
| if (!chip->data_ready && (file->f_flags & O_NONBLOCK)) { |
| rc = -EAGAIN; |
| goto fail_read; |
| } |
| |
| rc = wait_event_interruptible(chip->bms_wait_q, chip->data_ready); |
| if (rc) { |
| pr_debug("wait failed! rc=%d\n", rc); |
| goto fail_read; |
| } |
| |
| if (!chip->data_ready) { |
| pr_debug("No Data, false wakeup\n"); |
| rc = -EFAULT; |
| goto fail_read; |
| } |
| |
| mutex_lock(&chip->bms_data_mutex); |
| |
| if (copy_to_user(buf, &chip->bms_data, sizeof(chip->bms_data))) { |
| pr_err("Failed in copy_to_user\n"); |
| mutex_unlock(&chip->bms_data_mutex); |
| rc = -EFAULT; |
| goto fail_read; |
| } |
| pr_debug("Data copied!!\n"); |
| chip->data_ready = 0; |
| |
| mutex_unlock(&chip->bms_data_mutex); |
| /* wakelock-timeout for userspace to pick up */ |
| pm_wakeup_event(chip->dev, BMS_READ_TIMEOUT); |
| |
| return sizeof(chip->bms_data); |
| |
| fail_read: |
|