diff --git a/Documentation/devicetree/bindings/power/qcom-charger/qpnp-fg-gen3.txt b/Documentation/devicetree/bindings/power/qcom-charger/qpnp-fg-gen3.txt index 7841251c67fe..9d2e067e784c 100644 --- a/Documentation/devicetree/bindings/power/qcom-charger/qpnp-fg-gen3.txt +++ b/Documentation/devicetree/bindings/power/qcom-charger/qpnp-fg-gen3.txt @@ -216,6 +216,12 @@ First Level Node - FG Gen3 device Definition: Battery temperature delta interrupt threshold. Possible values are: 2, 4, 6 and 10. Unit is in Kelvin. +- qcom,hold-soc-while-full: + Usage: optional + Value type: + Definition: A boolean property that when defined holds SOC at 100% when + the battery is full. + ========================================================== Second Level Nodes - Peripherals managed by FG Gen3 driver ========================================================== diff --git a/drivers/power/qcom-charger/fg-core.h b/drivers/power/qcom-charger/fg-core.h index 7e08b71e3b6a..41c444d09dea 100644 --- a/drivers/power/qcom-charger/fg-core.h +++ b/drivers/power/qcom-charger/fg-core.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,7 @@ pr_debug(fmt, ##__VA_ARGS__); \ } while (0) +/* Awake votable reasons */ #define SRAM_READ "fg_sram_read" #define SRAM_WRITE "fg_sram_write" #define PROFILE_LOAD "fg_profile_load" @@ -173,6 +175,8 @@ struct fg_alg_flag { /* DT parameters for FG device */ struct fg_dt_props { + bool force_load_profile; + bool hold_soc_while_full; int cutoff_volt_mv; int empty_volt_mv; int vbatt_low_thr_mv; @@ -185,7 +189,6 @@ struct fg_dt_props { int esr_timer_charging; int esr_timer_awake; int esr_timer_asleep; - bool force_load_profile; int cl_start_soc; int cl_max_temp; int cl_min_temp; @@ -240,6 +243,8 @@ struct fg_chip { struct dentry *dfs_root; struct power_supply *fg_psy; struct power_supply *batt_psy; + struct power_supply *usb_psy; + struct power_supply *dc_psy; struct iio_channel *batt_id_chan; struct fg_memif *sram; struct fg_irq_info *irqs; @@ -268,6 +273,8 @@ struct fg_chip { bool profile_loaded; bool battery_missing; bool fg_restarting; + bool charge_full; + bool recharge_soc_adjusted; struct completion soc_update; struct completion soc_ready; struct delayed_work profile_load_work; @@ -321,4 +328,5 @@ extern int fg_debugfs_create(struct fg_chip *chip); extern void fill_string(char *str, size_t str_len, u8 *buf, int buf_len); extern int64_t twos_compliment_extend(int64_t val, int s_bit_pos); extern s64 fg_float_decode(u16 val); +extern bool is_input_present(struct fg_chip *chip); #endif diff --git a/drivers/power/qcom-charger/fg-util.c b/drivers/power/qcom-charger/fg-util.c index 790e56bd3dae..bbdbe48896d7 100644 --- a/drivers/power/qcom-charger/fg-util.c +++ b/drivers/power/qcom-charger/fg-util.c @@ -29,6 +29,43 @@ static struct fg_dbgfs dbgfs_data = { }, }; +static bool is_usb_present(struct fg_chip *chip) +{ + union power_supply_propval pval = {0, }; + + if (!chip->usb_psy) + chip->usb_psy = power_supply_get_by_name("usb"); + + if (chip->usb_psy) + power_supply_get_property(chip->usb_psy, + POWER_SUPPLY_PROP_PRESENT, &pval); + else + return false; + + return pval.intval != 0; +} + +static bool is_dc_present(struct fg_chip *chip) +{ + union power_supply_propval pval = {0, }; + + if (!chip->dc_psy) + chip->dc_psy = power_supply_get_by_name("dc"); + + if (chip->dc_psy) + power_supply_get_property(chip->dc_psy, + POWER_SUPPLY_PROP_PRESENT, &pval); + else + return false; + + return pval.intval != 0; +} + +bool is_input_present(struct fg_chip *chip) +{ + return is_usb_present(chip) || is_dc_present(chip); +} + #define EXPONENT_SHIFT 11 #define EXPONENT_OFFSET -9 #define MANTISSA_SIGN_BIT 10 @@ -98,6 +135,7 @@ int fg_sram_write(struct fg_chip *chip, u16 address, u8 offset, * This interrupt need to be enabled only when it is * required. It will be kept disabled other times. */ + reinit_completion(&chip->soc_update); enable_irq(chip->irqs[SOC_UPDATE_IRQ].irq); atomic_access = true; } else { diff --git a/drivers/power/qcom-charger/qpnp-fg-gen3.c b/drivers/power/qcom-charger/qpnp-fg-gen3.c index f8c1ad5963af..fee036c77bc1 100644 --- a/drivers/power/qcom-charger/qpnp-fg-gen3.c +++ b/drivers/power/qcom-charger/qpnp-fg-gen3.c @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include "fg-core.h" @@ -65,6 +64,8 @@ #define PROFILE_INTEGRITY_OFFSET 3 #define BATT_SOC_WORD 91 #define BATT_SOC_OFFSET 0 +#define FULL_SOC_WORD 93 +#define FULL_SOC_OFFSET 2 #define MONOTONIC_SOC_WORD 94 #define MONOTONIC_SOC_OFFSET 2 #define CC_SOC_WORD 95 @@ -106,8 +107,6 @@ static int fg_decode_value_16b(struct fg_sram_param *sp, enum fg_sram_param_id id, int val); static int fg_decode_default(struct fg_sram_param *sp, enum fg_sram_param_id id, int val); -static int fg_decode_batt_soc(struct fg_sram_param *sp, - enum fg_sram_param_id id, int val); static int fg_decode_cc_soc(struct fg_sram_param *sp, enum fg_sram_param_id id, int value); static void fg_encode_voltage(struct fg_sram_param *sp, @@ -132,7 +131,7 @@ static void fg_encode_default(struct fg_sram_param *sp, static struct fg_sram_param pmicobalt_v1_sram_params[] = { PARAM(BATT_SOC, BATT_SOC_WORD, BATT_SOC_OFFSET, 4, 1, 1, 0, NULL, - fg_decode_batt_soc), + fg_decode_default), PARAM(VOLTAGE_PRED, VOLTAGE_PRED_WORD, VOLTAGE_PRED_OFFSET, 2, 244141, 1000, 0, NULL, fg_decode_voltage_15b), PARAM(OCV, OCV_WORD, OCV_OFFSET, 2, 244141, 1000, 0, NULL, @@ -178,7 +177,7 @@ static struct fg_sram_param pmicobalt_v1_sram_params[] = { static struct fg_sram_param pmicobalt_v2_sram_params[] = { PARAM(BATT_SOC, BATT_SOC_WORD, BATT_SOC_OFFSET, 4, 1, 1, 0, NULL, - fg_decode_batt_soc), + fg_decode_default), PARAM(VOLTAGE_PRED, VOLTAGE_PRED_WORD, VOLTAGE_PRED_OFFSET, 2, 244141, 1000, 0, NULL, fg_decode_voltage_15b), PARAM(OCV, OCV_WORD, OCV_OFFSET, 2, 244141, 1000, 0, NULL, @@ -335,19 +334,11 @@ static int fg_decode_value_16b(struct fg_sram_param *sp, return sp[id].value; } -static int fg_decode_batt_soc(struct fg_sram_param *sp, - enum fg_sram_param_id id, int value) -{ - sp[id].value = (u32)value >> 24; - pr_debug("id: %d raw value: %x decoded value: %x\n", id, value, - sp[id].value); - return sp[id].value; -} - static int fg_decode_default(struct fg_sram_param *sp, enum fg_sram_param_id id, int value) { - return value; + sp[id].value = value; + return sp[id].value; } static int fg_decode(struct fg_sram_param *sp, enum fg_sram_param_id id, @@ -645,6 +636,11 @@ static int fg_get_prop_capacity(struct fg_chip *chip, int *val) { int rc, msoc; + if (chip->charge_full) { + *val = FULL_CAPACITY; + return 0; + } + rc = fg_get_msoc_raw(chip, &msoc); if (rc < 0) return rc; @@ -1059,7 +1055,7 @@ static int fg_cap_learning_done(struct fg_chip *chip) cc_soc_sw = CC_SOC_30BIT; rc = fg_sram_write(chip, chip->sp[FG_SRAM_CC_SOC_SW].addr_word, chip->sp[FG_SRAM_CC_SOC_SW].addr_byte, (u8 *)&cc_soc_sw, - chip->sp[FG_SRAM_CC_SOC_SW].len, FG_IMA_DEFAULT); + chip->sp[FG_SRAM_CC_SOC_SW].len, FG_IMA_ATOMIC); if (rc < 0) { pr_err("Error in writing cc_soc_sw, rc=%d\n", rc); goto out; @@ -1092,6 +1088,9 @@ static void fg_cap_learning_update(struct fg_chip *chip) goto out; } + /* We need only the most significant byte here */ + batt_soc = (u32)batt_soc >> 24; + fg_dbg(chip, FG_CAP_LEARN, "Chg_status: %d cl_active: %d batt_soc: %d\n", chip->status, chip->cl.active, batt_soc); @@ -1103,8 +1102,7 @@ static void fg_cap_learning_update(struct fg_chip *chip) } } else { - if (chip->status == POWER_SUPPLY_STATUS_FULL && - chip->charge_done) { + if (chip->charge_done) { rc = fg_cap_learning_done(chip); if (rc < 0) pr_err("Error in completing capacity learning, rc=%d\n", @@ -1126,19 +1124,157 @@ out: mutex_unlock(&chip->cl.lock); } +static int fg_charge_full_update(struct fg_chip *chip) +{ + union power_supply_propval prop = {0, }; + int rc, msoc, bsoc, recharge_soc; + u8 full_soc[2] = {0xFF, 0xFF}; + + if (!chip->dt.hold_soc_while_full) + return 0; + + if (!is_charger_available(chip)) + return 0; + + rc = power_supply_get_property(chip->batt_psy, POWER_SUPPLY_PROP_HEALTH, + &prop); + if (rc < 0) { + pr_err("Error in getting battery health, rc=%d\n", rc); + return rc; + } + + chip->health = prop.intval; + recharge_soc = chip->dt.recharge_soc_thr; + recharge_soc = DIV_ROUND_CLOSEST(recharge_soc * FULL_SOC_RAW, + FULL_CAPACITY); + rc = fg_get_sram_prop(chip, FG_SRAM_BATT_SOC, &bsoc); + if (rc < 0) { + pr_err("Error in getting BATT_SOC, rc=%d\n", rc); + return rc; + } + + /* We need 2 most significant bytes here */ + bsoc = (u32)bsoc >> 16; + rc = fg_get_prop_capacity(chip, &msoc); + if (rc < 0) { + pr_err("Error in getting capacity, rc=%d\n", rc); + return rc; + } + + fg_dbg(chip, FG_STATUS, "msoc: %d health: %d status: %d\n", msoc, + chip->health, chip->status); + if (chip->charge_done) { + if (msoc >= 99 && chip->health == POWER_SUPPLY_HEALTH_GOOD) + chip->charge_full = true; + else + fg_dbg(chip, FG_STATUS, "Terminated charging @ SOC%d\n", + msoc); + } else if ((bsoc >> 8) <= recharge_soc) { + fg_dbg(chip, FG_STATUS, "bsoc: %d recharge_soc: %d\n", + bsoc >> 8, recharge_soc); + chip->charge_full = false; + } + + if (!chip->charge_full) + return 0; + + /* + * During JEITA conditions, charge_full can happen early. FULL_SOC + * and MONOTONIC_SOC needs to be updated to reflect the same. Write + * battery SOC to FULL_SOC and write a full value to MONOTONIC_SOC. + */ + rc = fg_sram_write(chip, FULL_SOC_WORD, FULL_SOC_OFFSET, (u8 *)&bsoc, 2, + FG_IMA_ATOMIC); + if (rc < 0) { + pr_err("failed to write full_soc rc=%d\n", rc); + return rc; + } + + rc = fg_sram_write(chip, MONOTONIC_SOC_WORD, MONOTONIC_SOC_OFFSET, + full_soc, 2, FG_IMA_ATOMIC); + if (rc < 0) { + pr_err("failed to write monotonic_soc rc=%d\n", rc); + return rc; + } + + fg_dbg(chip, FG_STATUS, "Set charge_full to true @ soc %d\n", msoc); + return 0; +} + +static int fg_set_recharge_soc(struct fg_chip *chip, int recharge_soc) +{ + u8 buf[4]; + int rc; + + fg_encode(chip->sp, FG_SRAM_RECHARGE_SOC_THR, recharge_soc, buf); + rc = fg_sram_write(chip, + chip->sp[FG_SRAM_RECHARGE_SOC_THR].addr_word, + chip->sp[FG_SRAM_RECHARGE_SOC_THR].addr_byte, buf, + chip->sp[FG_SRAM_RECHARGE_SOC_THR].len, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing recharge_soc_thr, rc=%d\n", rc); + return rc; + } + + return 0; +} + +static int fg_adjust_recharge_soc(struct fg_chip *chip) +{ + int rc, msoc, recharge_soc, new_recharge_soc = 0; + + recharge_soc = chip->dt.recharge_soc_thr; + /* + * If the input is present and charging had been terminated, adjust + * the recharge SOC threshold based on the monotonic SOC at which + * the charge termination had happened. + */ + if (is_input_present(chip) && !chip->recharge_soc_adjusted + && chip->charge_done) { + /* Get raw monotonic SOC for calculation */ + rc = fg_get_msoc_raw(chip, &msoc); + if (rc < 0) { + pr_err("Error in getting msoc, rc=%d\n", rc); + return rc; + } + + msoc = DIV_ROUND_CLOSEST(msoc * FULL_CAPACITY, FULL_SOC_RAW); + /* Adjust the recharge_soc threshold */ + new_recharge_soc = msoc - (FULL_CAPACITY - recharge_soc); + } else if (chip->recharge_soc_adjusted && (!is_input_present(chip) + || chip->health == POWER_SUPPLY_HEALTH_GOOD)) { + /* Restore the default value */ + new_recharge_soc = recharge_soc; + } + + if (new_recharge_soc > 0 && new_recharge_soc < FULL_CAPACITY) { + rc = fg_set_recharge_soc(chip, new_recharge_soc); + if (rc) { + pr_err("Couldn't set resume SOC for FG, rc=%d\n", rc); + return rc; + } + + chip->recharge_soc_adjusted = (new_recharge_soc != + recharge_soc); + fg_dbg(chip, FG_STATUS, "resume soc set to %d\n", + new_recharge_soc); + } + + return 0; +} + static void status_change_work(struct work_struct *work) { struct fg_chip *chip = container_of(work, struct fg_chip, status_change_work); union power_supply_propval prop = {0, }; - int prev_status, rc; + int rc; if (!is_charger_available(chip)) { fg_dbg(chip, FG_STATUS, "Charger not available?!\n"); goto out; } - prev_status = chip->status; rc = power_supply_get_property(chip->batt_psy, POWER_SUPPLY_PROP_STATUS, &prop); if (rc < 0) { @@ -1155,13 +1291,20 @@ static void status_change_work(struct work_struct *work) } chip->charge_done = prop.intval; - fg_dbg(chip, FG_POWER_SUPPLY, "prev_status: %d curr_status:%d charge_done: %d\n", - prev_status, chip->status, chip->charge_done); - if (prev_status != chip->status) { - if (chip->cyc_ctr.en) - schedule_work(&chip->cycle_count_work); - fg_cap_learning_update(chip); - } + fg_dbg(chip, FG_POWER_SUPPLY, "curr_status:%d charge_done: %d\n", + chip->status, chip->charge_done); + + if (chip->cyc_ctr.en) + schedule_work(&chip->cycle_count_work); + + fg_cap_learning_update(chip); + rc = fg_charge_full_update(chip); + if (rc < 0) + pr_err("Error in charge_full_update, rc=%d\n", rc); + + rc = fg_adjust_recharge_soc(chip); + if (rc < 0) + pr_err("Error in adjusting recharge_soc, rc=%d\n", rc); out: pm_relax(chip->dev); @@ -1247,6 +1390,9 @@ static void cycle_count_work(struct work_struct *work) goto out; } + /* We need only the most significant byte here */ + batt_soc = (u32)batt_soc >> 24; + if (chip->status == POWER_SUPPLY_STATUS_CHARGING) { /* Find out which bucket the SOC falls in */ bucket = batt_soc / BUCKET_SOC_PCT; @@ -1787,16 +1933,9 @@ static int fg_hw_init(struct fg_chip *chip) } if (chip->dt.recharge_soc_thr > 0 && chip->dt.recharge_soc_thr < 100) { - fg_encode(chip->sp, FG_SRAM_RECHARGE_SOC_THR, - chip->dt.recharge_soc_thr, buf); - rc = fg_sram_write(chip, - chip->sp[FG_SRAM_RECHARGE_SOC_THR].addr_word, - chip->sp[FG_SRAM_RECHARGE_SOC_THR].addr_byte, - buf, chip->sp[FG_SRAM_RECHARGE_SOC_THR].len, - FG_IMA_DEFAULT); + rc = fg_set_recharge_soc(chip, chip->dt.recharge_soc_thr); if (rc < 0) { - pr_err("Error in writing recharge_soc_thr, rc=%d\n", - rc); + pr_err("Error in setting recharge_soc, rc=%d\n", rc); return rc; } } @@ -1982,6 +2121,7 @@ static irqreturn_t fg_soc_update_irq_handler(int irq, void *data) static irqreturn_t fg_delta_soc_irq_handler(int irq, void *data) { struct fg_chip *chip = data; + int rc; if (chip->cyc_ctr.en) schedule_work(&chip->cycle_count_work); @@ -1994,6 +2134,10 @@ static irqreturn_t fg_delta_soc_irq_handler(int irq, void *data) if (chip->cl.active) fg_cap_learning_update(chip); + rc = fg_charge_full_update(chip); + if (rc < 0) + pr_err("Error in charge_full_update, rc=%d\n", rc); + return IRQ_HANDLED; } @@ -2416,6 +2560,8 @@ static int fg_parse_dt(struct fg_chip *chip) else if (temp > BTEMP_DELTA_LOW && temp <= BTEMP_DELTA_HIGH) chip->dt.batt_temp_delta = temp; + chip->dt.hold_soc_while_full = of_property_read_bool(node, + "qcom,hold-soc-while-full"); return 0; }