Loading Documentation/devicetree/bindings/power/qpnp-fg.txt +5 −0 Original line number Diff line number Diff line Loading @@ -250,6 +250,11 @@ Parent node optional properties: If qcom,fg-dischg-voltage-gain-ctrl is set, then this property should be specified to apply the gain setting. - qcom,fg-use-vbat-low-empty-soc: A boolean property to specify whether vbatt-low interrupt is used to handle empty battery condition. If this is not specified, empty battery condition is detected by empty-soc interrupt. qcom,fg-soc node required properties: - reg : offset and length of the PMIC peripheral register map. Loading drivers/power/qpnp-fg.c +128 −38 Original line number Diff line number Diff line Loading @@ -481,6 +481,7 @@ struct fg_chip { bool bad_batt_detection_en; bool bcl_lpm_disabled; bool charging_disabled; bool use_vbat_low_empty_soc; struct delayed_work update_jeita_setting; struct delayed_work update_sram_data; struct delayed_work update_temp_work; Loading Loading @@ -1717,14 +1718,37 @@ out: return rc; } #define VBATT_LOW_STS_BIT BIT(2) static int fg_get_vbatt_status(struct fg_chip *chip, bool *vbatt_low_sts) { int rc = 0; u8 fg_batt_sts; rc = fg_read(chip, &fg_batt_sts, INT_RT_STS(chip->batt_base), 1); if (rc) pr_err("spmi read failed: addr=%03X, rc=%d\n", INT_RT_STS(chip->batt_base), rc); else *vbatt_low_sts = !!(fg_batt_sts & VBATT_LOW_STS_BIT); return rc; } #define SOC_EMPTY BIT(3) static bool fg_is_batt_empty(struct fg_chip *chip) { u8 fg_soc_sts; int rc; bool vbatt_low_sts; rc = fg_read(chip, &fg_soc_sts, INT_RT_STS(chip->soc_base), 1); if (chip->use_vbat_low_empty_soc) { if (fg_get_vbatt_status(chip, &vbatt_low_sts)) return false; return vbatt_low_sts; } rc = fg_read(chip, &fg_soc_sts, INT_RT_STS(chip->soc_base), 1); if (rc) { pr_err("spmi read failed: addr=%03X, rc=%d\n", INT_RT_STS(chip->soc_base), rc); Loading Loading @@ -2270,18 +2294,6 @@ static int fg_set_resume_soc(struct fg_chip *chip, u8 threshold) return rc; } #define VBATT_LOW_STS_BIT BIT(2) static int fg_get_vbatt_status(struct fg_chip *chip, bool *vbatt_low_sts) { int rc = 0; u8 fg_batt_sts; rc = fg_read(chip, &fg_batt_sts, INT_RT_STS(chip->batt_base), 1); if (!rc) *vbatt_low_sts = !!(fg_batt_sts & VBATT_LOW_STS_BIT); return rc; } #define BATT_CYCLE_NUMBER_REG 0x5E8 #define BATT_CYCLE_OFFSET 0 static void restore_cycle_counter(struct fg_chip *chip) Loading Loading @@ -3517,7 +3529,8 @@ static void status_change_work(struct work_struct *work) } if (chip->status == POWER_SUPPLY_STATUS_FULL || chip->status == POWER_SUPPLY_STATUS_CHARGING) { if (!chip->vbat_low_irq_enabled) { if (!chip->vbat_low_irq_enabled && !chip->use_vbat_low_empty_soc) { enable_irq(chip->batt_irq[VBATT_LOW].irq); enable_irq_wake(chip->batt_irq[VBATT_LOW].irq); chip->vbat_low_irq_enabled = true; Loading @@ -3525,7 +3538,8 @@ static void status_change_work(struct work_struct *work) if (!!(chip->wa_flag & PULSE_REQUEST_WA) && capacity == 100) fg_configure_soc(chip); } else if (chip->status == POWER_SUPPLY_STATUS_DISCHARGING) { if (chip->vbat_low_irq_enabled) { if (chip->vbat_low_irq_enabled && !chip->use_vbat_low_empty_soc) { disable_irq_wake(chip->batt_irq[VBATT_LOW].irq); disable_irq_nosync(chip->batt_irq[VBATT_LOW].irq); chip->vbat_low_irq_enabled = false; Loading Loading @@ -4018,21 +4032,40 @@ static bool is_first_est_done(struct fg_chip *chip) return (fg_soc_sts & SOC_FIRST_EST_DONE) ? true : false; } #define FG_EMPTY_DEBOUNCE_MS 1500 static irqreturn_t fg_vbatt_low_handler(int irq, void *_chip) { struct fg_chip *chip = _chip; int rc; bool vbatt_low_sts; if (fg_debug_mask & FG_IRQS) pr_info("vbatt-low triggered\n"); if (chip->status == POWER_SUPPLY_STATUS_CHARGING) { rc = fg_get_vbatt_status(chip, &vbatt_low_sts); if (rc) { pr_err("error in reading vbatt_status, rc:%d\n", rc); /* handle empty soc based on vbatt-low interrupt */ if (chip->use_vbat_low_empty_soc) { if (fg_get_vbatt_status(chip, &vbatt_low_sts)) goto out; if (vbatt_low_sts) { if (fg_debug_mask & FG_IRQS) pr_info("Vbatt is low\n"); disable_irq_wake(chip->batt_irq[VBATT_LOW].irq); disable_irq_nosync(chip->batt_irq[VBATT_LOW].irq); chip->vbat_low_irq_enabled = false; fg_stay_awake(&chip->empty_check_wakeup_source); schedule_delayed_work(&chip->check_empty_work, msecs_to_jiffies(FG_EMPTY_DEBOUNCE_MS)); } else { if (fg_debug_mask & FG_IRQS) pr_info("Vbatt is high\n"); chip->soc_empty = false; } goto out; } if (chip->status == POWER_SUPPLY_STATUS_CHARGING) { if (fg_get_vbatt_status(chip, &vbatt_low_sts)) goto out; if (!vbatt_low_sts && chip->vbat_low_irq_enabled) { if (fg_debug_mask & FG_IRQS) pr_info("disabling vbatt_low irq\n"); Loading Loading @@ -4121,7 +4154,7 @@ static irqreturn_t fg_soc_irq_handler(int irq, void *_chip) { struct fg_chip *chip = _chip; u8 soc_rt_sts; int rc; int rc, msoc; rc = fg_read(chip, &soc_rt_sts, INT_RT_STS(chip->soc_base), 1); if (rc) { Loading @@ -4142,6 +4175,15 @@ static irqreturn_t fg_soc_irq_handler(int irq, void *_chip) schedule_work(&chip->slope_limiter_work); } if (chip->use_vbat_low_empty_soc) { msoc = get_monotonic_soc_raw(chip); if (msoc == 0 || chip->soc_empty) { fg_stay_awake(&chip->empty_check_wakeup_source); schedule_delayed_work(&chip->check_empty_work, msecs_to_jiffies(FG_EMPTY_DEBOUNCE_MS)); } } schedule_work(&chip->battery_age_work); if (chip->power_supply_registered) Loading Loading @@ -4180,7 +4222,6 @@ static irqreturn_t fg_soc_irq_handler(int irq, void *_chip) return IRQ_HANDLED; } #define FG_EMPTY_DEBOUNCE_MS 1500 static irqreturn_t fg_empty_soc_irq_handler(int irq, void *_chip) { struct fg_chip *chip = _chip; Loading Loading @@ -5390,14 +5431,41 @@ static void check_empty_work(struct work_struct *work) struct fg_chip *chip = container_of(work, struct fg_chip, check_empty_work.work); bool vbatt_low_sts; int msoc; /* handle empty soc based on vbatt-low interrupt */ if (chip->use_vbat_low_empty_soc) { if (fg_get_vbatt_status(chip, &vbatt_low_sts)) goto out; msoc = get_monotonic_soc_raw(chip); if (fg_is_batt_empty(chip)) { if (fg_debug_mask & FG_STATUS) pr_info("Vbatt_low: %d, msoc: %d\n", vbatt_low_sts, msoc); if (vbatt_low_sts || (msoc == 0)) chip->soc_empty = true; else chip->soc_empty = false; if (chip->power_supply_registered) power_supply_changed(&chip->bms_psy); if (!chip->vbat_low_irq_enabled) { enable_irq(chip->batt_irq[VBATT_LOW].irq); enable_irq_wake(chip->batt_irq[VBATT_LOW].irq); chip->vbat_low_irq_enabled = true; } } else if (fg_is_batt_empty(chip)) { if (fg_debug_mask & FG_STATUS) pr_info("EMPTY SOC high\n"); chip->soc_empty = true; if (chip->power_supply_registered) power_supply_changed(&chip->bms_psy); } out: fg_relax(&chip->empty_check_wakeup_source); } Loading Loading @@ -5932,6 +6000,9 @@ static int fg_of_init(struct fg_chip *chip) } } chip->use_vbat_low_empty_soc = of_property_read_bool(node, "qcom,fg-use-vbat-low-empty-soc"); return rc; } Loading Loading @@ -5980,7 +6051,7 @@ static int fg_init_irqs(struct fg_chip *chip) chip->soc_irq[EMPTY_SOC].irq = spmi_get_irq_byname( chip->spmi, spmi_resource, "empty-soc"); if (chip->soc_irq[EMPTY_SOC].irq < 0) { pr_err("Unable to get low-soc irq\n"); pr_err("Unable to get empty-soc irq\n"); return rc; } chip->soc_irq[DELTA_SOC].irq = spmi_get_irq_byname( Loading @@ -6005,16 +6076,22 @@ static int fg_init_irqs(struct fg_chip *chip) chip->soc_irq[FULL_SOC].irq, rc); return rc; } if (!chip->use_vbat_low_empty_soc) { rc = devm_request_irq(chip->dev, chip->soc_irq[EMPTY_SOC].irq, fg_empty_soc_irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "empty-soc", chip); if (rc < 0) { pr_err("Can't request %d empty-soc: %d\n", chip->soc_irq[EMPTY_SOC].irq, rc); chip->soc_irq[EMPTY_SOC].irq, rc); return rc; } } rc = devm_request_irq(chip->dev, chip->soc_irq[DELTA_SOC].irq, fg_soc_irq_handler, IRQF_TRIGGER_RISING, Loading @@ -6036,6 +6113,7 @@ static int fg_init_irqs(struct fg_chip *chip) enable_irq_wake(chip->soc_irq[DELTA_SOC].irq); enable_irq_wake(chip->soc_irq[FULL_SOC].irq); if (!chip->use_vbat_low_empty_soc) enable_irq_wake(chip->soc_irq[EMPTY_SOC].irq); break; case FG_MEMIF: Loading Loading @@ -6098,8 +6176,14 @@ static int fg_init_irqs(struct fg_chip *chip) chip->batt_irq[VBATT_LOW].irq, rc); return rc; } disable_irq_nosync(chip->batt_irq[VBATT_LOW].irq); if (chip->use_vbat_low_empty_soc) { enable_irq_wake(chip->batt_irq[VBATT_LOW].irq); chip->vbat_low_irq_enabled = true; } else { disable_irq_nosync( chip->batt_irq[VBATT_LOW].irq); chip->vbat_low_irq_enabled = false; } break; case FG_ADC: break; Loading Loading @@ -6633,8 +6717,9 @@ static int fg_common_hw_init(struct fg_chip *chip) update_iterm(chip); update_cutoff_voltage(chip); update_irq_volt_empty(chip); update_bcl_thresholds(chip); if (!chip->use_vbat_low_empty_soc) update_irq_volt_empty(chip); resume_soc_raw = settings[FG_MEM_RESUME_SOC].value; if (resume_soc_raw > 0) { Loading Loading @@ -6664,6 +6749,11 @@ static int fg_common_hw_init(struct fg_chip *chip) return rc; } /* Override the voltage threshold for vbatt_low with empty_volt */ if (chip->use_vbat_low_empty_soc) settings[FG_MEM_BATT_LOW].value = settings[FG_MEM_IRQ_VOLT_EMPTY].value; rc = fg_mem_masked_write(chip, settings[FG_MEM_BATT_LOW].address, 0xFF, batt_to_setpoint_8b(settings[FG_MEM_BATT_LOW].value), settings[FG_MEM_BATT_LOW].offset); Loading Loading
Documentation/devicetree/bindings/power/qpnp-fg.txt +5 −0 Original line number Diff line number Diff line Loading @@ -250,6 +250,11 @@ Parent node optional properties: If qcom,fg-dischg-voltage-gain-ctrl is set, then this property should be specified to apply the gain setting. - qcom,fg-use-vbat-low-empty-soc: A boolean property to specify whether vbatt-low interrupt is used to handle empty battery condition. If this is not specified, empty battery condition is detected by empty-soc interrupt. qcom,fg-soc node required properties: - reg : offset and length of the PMIC peripheral register map. Loading
drivers/power/qpnp-fg.c +128 −38 Original line number Diff line number Diff line Loading @@ -481,6 +481,7 @@ struct fg_chip { bool bad_batt_detection_en; bool bcl_lpm_disabled; bool charging_disabled; bool use_vbat_low_empty_soc; struct delayed_work update_jeita_setting; struct delayed_work update_sram_data; struct delayed_work update_temp_work; Loading Loading @@ -1717,14 +1718,37 @@ out: return rc; } #define VBATT_LOW_STS_BIT BIT(2) static int fg_get_vbatt_status(struct fg_chip *chip, bool *vbatt_low_sts) { int rc = 0; u8 fg_batt_sts; rc = fg_read(chip, &fg_batt_sts, INT_RT_STS(chip->batt_base), 1); if (rc) pr_err("spmi read failed: addr=%03X, rc=%d\n", INT_RT_STS(chip->batt_base), rc); else *vbatt_low_sts = !!(fg_batt_sts & VBATT_LOW_STS_BIT); return rc; } #define SOC_EMPTY BIT(3) static bool fg_is_batt_empty(struct fg_chip *chip) { u8 fg_soc_sts; int rc; bool vbatt_low_sts; rc = fg_read(chip, &fg_soc_sts, INT_RT_STS(chip->soc_base), 1); if (chip->use_vbat_low_empty_soc) { if (fg_get_vbatt_status(chip, &vbatt_low_sts)) return false; return vbatt_low_sts; } rc = fg_read(chip, &fg_soc_sts, INT_RT_STS(chip->soc_base), 1); if (rc) { pr_err("spmi read failed: addr=%03X, rc=%d\n", INT_RT_STS(chip->soc_base), rc); Loading Loading @@ -2270,18 +2294,6 @@ static int fg_set_resume_soc(struct fg_chip *chip, u8 threshold) return rc; } #define VBATT_LOW_STS_BIT BIT(2) static int fg_get_vbatt_status(struct fg_chip *chip, bool *vbatt_low_sts) { int rc = 0; u8 fg_batt_sts; rc = fg_read(chip, &fg_batt_sts, INT_RT_STS(chip->batt_base), 1); if (!rc) *vbatt_low_sts = !!(fg_batt_sts & VBATT_LOW_STS_BIT); return rc; } #define BATT_CYCLE_NUMBER_REG 0x5E8 #define BATT_CYCLE_OFFSET 0 static void restore_cycle_counter(struct fg_chip *chip) Loading Loading @@ -3517,7 +3529,8 @@ static void status_change_work(struct work_struct *work) } if (chip->status == POWER_SUPPLY_STATUS_FULL || chip->status == POWER_SUPPLY_STATUS_CHARGING) { if (!chip->vbat_low_irq_enabled) { if (!chip->vbat_low_irq_enabled && !chip->use_vbat_low_empty_soc) { enable_irq(chip->batt_irq[VBATT_LOW].irq); enable_irq_wake(chip->batt_irq[VBATT_LOW].irq); chip->vbat_low_irq_enabled = true; Loading @@ -3525,7 +3538,8 @@ static void status_change_work(struct work_struct *work) if (!!(chip->wa_flag & PULSE_REQUEST_WA) && capacity == 100) fg_configure_soc(chip); } else if (chip->status == POWER_SUPPLY_STATUS_DISCHARGING) { if (chip->vbat_low_irq_enabled) { if (chip->vbat_low_irq_enabled && !chip->use_vbat_low_empty_soc) { disable_irq_wake(chip->batt_irq[VBATT_LOW].irq); disable_irq_nosync(chip->batt_irq[VBATT_LOW].irq); chip->vbat_low_irq_enabled = false; Loading Loading @@ -4018,21 +4032,40 @@ static bool is_first_est_done(struct fg_chip *chip) return (fg_soc_sts & SOC_FIRST_EST_DONE) ? true : false; } #define FG_EMPTY_DEBOUNCE_MS 1500 static irqreturn_t fg_vbatt_low_handler(int irq, void *_chip) { struct fg_chip *chip = _chip; int rc; bool vbatt_low_sts; if (fg_debug_mask & FG_IRQS) pr_info("vbatt-low triggered\n"); if (chip->status == POWER_SUPPLY_STATUS_CHARGING) { rc = fg_get_vbatt_status(chip, &vbatt_low_sts); if (rc) { pr_err("error in reading vbatt_status, rc:%d\n", rc); /* handle empty soc based on vbatt-low interrupt */ if (chip->use_vbat_low_empty_soc) { if (fg_get_vbatt_status(chip, &vbatt_low_sts)) goto out; if (vbatt_low_sts) { if (fg_debug_mask & FG_IRQS) pr_info("Vbatt is low\n"); disable_irq_wake(chip->batt_irq[VBATT_LOW].irq); disable_irq_nosync(chip->batt_irq[VBATT_LOW].irq); chip->vbat_low_irq_enabled = false; fg_stay_awake(&chip->empty_check_wakeup_source); schedule_delayed_work(&chip->check_empty_work, msecs_to_jiffies(FG_EMPTY_DEBOUNCE_MS)); } else { if (fg_debug_mask & FG_IRQS) pr_info("Vbatt is high\n"); chip->soc_empty = false; } goto out; } if (chip->status == POWER_SUPPLY_STATUS_CHARGING) { if (fg_get_vbatt_status(chip, &vbatt_low_sts)) goto out; if (!vbatt_low_sts && chip->vbat_low_irq_enabled) { if (fg_debug_mask & FG_IRQS) pr_info("disabling vbatt_low irq\n"); Loading Loading @@ -4121,7 +4154,7 @@ static irqreturn_t fg_soc_irq_handler(int irq, void *_chip) { struct fg_chip *chip = _chip; u8 soc_rt_sts; int rc; int rc, msoc; rc = fg_read(chip, &soc_rt_sts, INT_RT_STS(chip->soc_base), 1); if (rc) { Loading @@ -4142,6 +4175,15 @@ static irqreturn_t fg_soc_irq_handler(int irq, void *_chip) schedule_work(&chip->slope_limiter_work); } if (chip->use_vbat_low_empty_soc) { msoc = get_monotonic_soc_raw(chip); if (msoc == 0 || chip->soc_empty) { fg_stay_awake(&chip->empty_check_wakeup_source); schedule_delayed_work(&chip->check_empty_work, msecs_to_jiffies(FG_EMPTY_DEBOUNCE_MS)); } } schedule_work(&chip->battery_age_work); if (chip->power_supply_registered) Loading Loading @@ -4180,7 +4222,6 @@ static irqreturn_t fg_soc_irq_handler(int irq, void *_chip) return IRQ_HANDLED; } #define FG_EMPTY_DEBOUNCE_MS 1500 static irqreturn_t fg_empty_soc_irq_handler(int irq, void *_chip) { struct fg_chip *chip = _chip; Loading Loading @@ -5390,14 +5431,41 @@ static void check_empty_work(struct work_struct *work) struct fg_chip *chip = container_of(work, struct fg_chip, check_empty_work.work); bool vbatt_low_sts; int msoc; /* handle empty soc based on vbatt-low interrupt */ if (chip->use_vbat_low_empty_soc) { if (fg_get_vbatt_status(chip, &vbatt_low_sts)) goto out; msoc = get_monotonic_soc_raw(chip); if (fg_is_batt_empty(chip)) { if (fg_debug_mask & FG_STATUS) pr_info("Vbatt_low: %d, msoc: %d\n", vbatt_low_sts, msoc); if (vbatt_low_sts || (msoc == 0)) chip->soc_empty = true; else chip->soc_empty = false; if (chip->power_supply_registered) power_supply_changed(&chip->bms_psy); if (!chip->vbat_low_irq_enabled) { enable_irq(chip->batt_irq[VBATT_LOW].irq); enable_irq_wake(chip->batt_irq[VBATT_LOW].irq); chip->vbat_low_irq_enabled = true; } } else if (fg_is_batt_empty(chip)) { if (fg_debug_mask & FG_STATUS) pr_info("EMPTY SOC high\n"); chip->soc_empty = true; if (chip->power_supply_registered) power_supply_changed(&chip->bms_psy); } out: fg_relax(&chip->empty_check_wakeup_source); } Loading Loading @@ -5932,6 +6000,9 @@ static int fg_of_init(struct fg_chip *chip) } } chip->use_vbat_low_empty_soc = of_property_read_bool(node, "qcom,fg-use-vbat-low-empty-soc"); return rc; } Loading Loading @@ -5980,7 +6051,7 @@ static int fg_init_irqs(struct fg_chip *chip) chip->soc_irq[EMPTY_SOC].irq = spmi_get_irq_byname( chip->spmi, spmi_resource, "empty-soc"); if (chip->soc_irq[EMPTY_SOC].irq < 0) { pr_err("Unable to get low-soc irq\n"); pr_err("Unable to get empty-soc irq\n"); return rc; } chip->soc_irq[DELTA_SOC].irq = spmi_get_irq_byname( Loading @@ -6005,16 +6076,22 @@ static int fg_init_irqs(struct fg_chip *chip) chip->soc_irq[FULL_SOC].irq, rc); return rc; } if (!chip->use_vbat_low_empty_soc) { rc = devm_request_irq(chip->dev, chip->soc_irq[EMPTY_SOC].irq, fg_empty_soc_irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "empty-soc", chip); if (rc < 0) { pr_err("Can't request %d empty-soc: %d\n", chip->soc_irq[EMPTY_SOC].irq, rc); chip->soc_irq[EMPTY_SOC].irq, rc); return rc; } } rc = devm_request_irq(chip->dev, chip->soc_irq[DELTA_SOC].irq, fg_soc_irq_handler, IRQF_TRIGGER_RISING, Loading @@ -6036,6 +6113,7 @@ static int fg_init_irqs(struct fg_chip *chip) enable_irq_wake(chip->soc_irq[DELTA_SOC].irq); enable_irq_wake(chip->soc_irq[FULL_SOC].irq); if (!chip->use_vbat_low_empty_soc) enable_irq_wake(chip->soc_irq[EMPTY_SOC].irq); break; case FG_MEMIF: Loading Loading @@ -6098,8 +6176,14 @@ static int fg_init_irqs(struct fg_chip *chip) chip->batt_irq[VBATT_LOW].irq, rc); return rc; } disable_irq_nosync(chip->batt_irq[VBATT_LOW].irq); if (chip->use_vbat_low_empty_soc) { enable_irq_wake(chip->batt_irq[VBATT_LOW].irq); chip->vbat_low_irq_enabled = true; } else { disable_irq_nosync( chip->batt_irq[VBATT_LOW].irq); chip->vbat_low_irq_enabled = false; } break; case FG_ADC: break; Loading Loading @@ -6633,8 +6717,9 @@ static int fg_common_hw_init(struct fg_chip *chip) update_iterm(chip); update_cutoff_voltage(chip); update_irq_volt_empty(chip); update_bcl_thresholds(chip); if (!chip->use_vbat_low_empty_soc) update_irq_volt_empty(chip); resume_soc_raw = settings[FG_MEM_RESUME_SOC].value; if (resume_soc_raw > 0) { Loading Loading @@ -6664,6 +6749,11 @@ static int fg_common_hw_init(struct fg_chip *chip) return rc; } /* Override the voltage threshold for vbatt_low with empty_volt */ if (chip->use_vbat_low_empty_soc) settings[FG_MEM_BATT_LOW].value = settings[FG_MEM_IRQ_VOLT_EMPTY].value; rc = fg_mem_masked_write(chip, settings[FG_MEM_BATT_LOW].address, 0xFF, batt_to_setpoint_8b(settings[FG_MEM_BATT_LOW].value), settings[FG_MEM_BATT_LOW].offset); Loading