Loading drivers/power/power_supply_sysfs.c +2 −0 Original line number Diff line number Diff line Loading @@ -297,6 +297,8 @@ static struct device_attribute power_supply_attrs[] = { POWER_SUPPLY_ATTR(hw_current_max), POWER_SUPPLY_ATTR(real_type), POWER_SUPPLY_ATTR(pr_swap), POWER_SUPPLY_ATTR(cc_step), POWER_SUPPLY_ATTR(cc_step_sel), /* Local extensions of type int64_t */ POWER_SUPPLY_ATTR(charge_counter_ext), /* Properties of type `const char *' */ Loading drivers/power/supply/qcom/fg-core.h +10 −1 Original line number Diff line number Diff line Loading @@ -81,6 +81,8 @@ #define BATT_THERM_NUM_COEFFS 3 #define MAX_CC_STEPS 20 /* Debug flag definitions */ enum fg_debug_flag { FG_IRQ = BIT(0), /* Show interrupts */ Loading Loading @@ -309,11 +311,16 @@ struct fg_irq_info { }; struct fg_circ_buf { int arr[20]; int arr[10]; int size; int head; }; struct fg_cc_step_data { int arr[MAX_CC_STEPS]; int sel; }; struct fg_pt { s32 x; s32 y; Loading Loading @@ -374,6 +381,7 @@ struct fg_chip { struct fg_cyc_ctr_data cyc_ctr; struct notifier_block nb; struct fg_cap_learning cl; struct fg_cc_step_data cc_step; struct mutex bus_lock; struct mutex sram_rw_lock; struct mutex batt_avg_lock; Loading Loading @@ -475,5 +483,6 @@ extern bool is_qnovo_en(struct fg_chip *chip); extern void fg_circ_buf_add(struct fg_circ_buf *, int); extern void fg_circ_buf_clr(struct fg_circ_buf *); extern int fg_circ_buf_avg(struct fg_circ_buf *, int *); extern int fg_circ_buf_median(struct fg_circ_buf *, int *); extern int fg_lerp(const struct fg_pt *, size_t, s32, s32 *); #endif drivers/power/supply/qcom/fg-util.c +34 −0 Original line number Diff line number Diff line Loading @@ -10,6 +10,7 @@ * GNU General Public License for more details. */ #include <linux/sort.h> #include "fg-core.h" void fg_circ_buf_add(struct fg_circ_buf *buf, int val) Loading Loading @@ -39,6 +40,39 @@ int fg_circ_buf_avg(struct fg_circ_buf *buf, int *avg) return 0; } static int cmp_int(const void *a, const void *b) { return *(int *)a - *(int *)b; } int fg_circ_buf_median(struct fg_circ_buf *buf, int *median) { int *temp; if (buf->size == 0) return -ENODATA; if (buf->size == 1) { *median = buf->arr[0]; return 0; } temp = kmalloc_array(buf->size, sizeof(*temp), GFP_KERNEL); if (!temp) return -ENOMEM; memcpy(temp, buf->arr, buf->size * sizeof(*temp)); sort(temp, buf->size, sizeof(*temp), cmp_int, NULL); if (buf->size % 2) *median = temp[buf->size / 2]; else *median = (temp[buf->size / 2 - 1] + temp[buf->size / 2]) / 2; kfree(temp); return 0; } int fg_lerp(const struct fg_pt *pts, size_t tablesize, s32 input, s32 *output) { int i; Loading drivers/power/supply/qcom/qpnp-fg-gen3.c +127 −0 Original line number Diff line number Diff line Loading @@ -75,6 +75,8 @@ #define ESR_TIMER_CHG_MAX_OFFSET 0 #define ESR_TIMER_CHG_INIT_WORD 18 #define ESR_TIMER_CHG_INIT_OFFSET 2 #define ESR_EXTRACTION_ENABLE_WORD 19 #define ESR_EXTRACTION_ENABLE_OFFSET 0 #define PROFILE_LOAD_WORD 24 #define PROFILE_LOAD_OFFSET 0 #define ESR_RSLOW_DISCHG_WORD 34 Loading Loading @@ -3033,6 +3035,89 @@ static int fg_esr_validate(struct fg_chip *chip) return 0; } static int fg_force_esr_meas(struct fg_chip *chip) { int rc; int esr_uohms; /* force esr extraction enable */ rc = fg_sram_masked_write(chip, ESR_EXTRACTION_ENABLE_WORD, ESR_EXTRACTION_ENABLE_OFFSET, BIT(0), BIT(0), FG_IMA_DEFAULT); if (rc < 0) { pr_err("failed to enable esr extn rc=%d\n", rc); return rc; } rc = fg_masked_write(chip, BATT_INFO_QNOVO_CFG(chip), LD_REG_CTRL_BIT, 0); if (rc < 0) { pr_err("Error in configuring qnovo_cfg rc=%d\n", rc); return rc; } rc = fg_masked_write(chip, BATT_INFO_TM_MISC1(chip), ESR_REQ_CTL_BIT | ESR_REQ_CTL_EN_BIT, ESR_REQ_CTL_BIT | ESR_REQ_CTL_EN_BIT); if (rc < 0) { pr_err("Error in configuring force ESR rc=%d\n", rc); return rc; } /* wait 1.5 seconds for hw to measure ESR */ msleep(1500); rc = fg_masked_write(chip, BATT_INFO_TM_MISC1(chip), ESR_REQ_CTL_BIT | ESR_REQ_CTL_EN_BIT, 0); if (rc < 0) { pr_err("Error in restoring force ESR rc=%d\n", rc); return rc; } rc = fg_masked_write(chip, BATT_INFO_QNOVO_CFG(chip), LD_REG_CTRL_BIT, LD_REG_CTRL_BIT); if (rc < 0) { pr_err("Error in restoring qnovo_cfg rc=%d\n", rc); return rc; } /* force esr extraction disable */ rc = fg_sram_masked_write(chip, ESR_EXTRACTION_ENABLE_WORD, ESR_EXTRACTION_ENABLE_OFFSET, BIT(0), 0, FG_IMA_DEFAULT); if (rc < 0) { pr_err("failed to disable esr extn rc=%d\n", rc); return rc; } fg_get_battery_resistance(chip, &esr_uohms); fg_dbg(chip, FG_STATUS, "ESR uohms = %d\n", esr_uohms); return rc; } static int fg_prepare_for_qnovo(struct fg_chip *chip, int qnovo_enable) { int rc; /* force esr extraction disable when qnovo enables */ rc = fg_sram_masked_write(chip, ESR_EXTRACTION_ENABLE_WORD, ESR_EXTRACTION_ENABLE_OFFSET, BIT(0), qnovo_enable ? 0 : BIT(0), FG_IMA_DEFAULT); if (rc < 0) pr_err("Error in configuring esr extraction rc=%d\n", rc); rc = fg_masked_write(chip, BATT_INFO_QNOVO_CFG(chip), LD_REG_CTRL_BIT, qnovo_enable ? LD_REG_CTRL_BIT : 0); if (rc < 0) { pr_err("Error in configuring qnovo_cfg rc=%d\n", rc); return rc; } fg_dbg(chip, FG_STATUS, "Prepared for Qnovo\n"); return 0; } /* PSY CALLBACKS STAY HERE */ static int fg_psy_get_property(struct power_supply *psy, Loading Loading @@ -3109,6 +3194,19 @@ static int fg_psy_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: rc = fg_get_sram_prop(chip, FG_SRAM_VBATT_FULL, &pval->intval); break; case POWER_SUPPLY_PROP_CC_STEP: if ((chip->cc_step.sel >= 0) && (chip->cc_step.sel < MAX_CC_STEPS)) { pval->intval = chip->cc_step.arr[chip->cc_step.sel]; } else { pr_err("cc_step_sel is out of bounds [0, %d]\n", chip->cc_step.sel); return -EINVAL; } break; case POWER_SUPPLY_PROP_CC_STEP_SEL: pval->intval = chip->cc_step.sel; break; default: pr_err("unsupported property %d\n", psp); rc = -EINVAL; Loading Loading @@ -3141,6 +3239,31 @@ static int fg_psy_set_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: rc = fg_set_constant_chg_voltage(chip, pval->intval); break; case POWER_SUPPLY_PROP_RESISTANCE: rc = fg_force_esr_meas(chip); break; case POWER_SUPPLY_PROP_CHARGE_QNOVO_ENABLE: rc = fg_prepare_for_qnovo(chip, pval->intval); break; case POWER_SUPPLY_PROP_CC_STEP: if ((chip->cc_step.sel >= 0) && (chip->cc_step.sel < MAX_CC_STEPS)) { chip->cc_step.arr[chip->cc_step.sel] = pval->intval; } else { pr_err("cc_step_sel is out of bounds [0, %d]\n", chip->cc_step.sel); return -EINVAL; } break; case POWER_SUPPLY_PROP_CC_STEP_SEL: if ((pval->intval >= 0) && (pval->intval < MAX_CC_STEPS)) { chip->cc_step.sel = pval->intval; } else { pr_err("cc_step_sel is out of bounds [0, %d]\n", pval->intval); return -EINVAL; } break; default: break; } Loading @@ -3154,6 +3277,8 @@ static int fg_property_is_writeable(struct power_supply *psy, switch (psp) { case POWER_SUPPLY_PROP_CYCLE_COUNT_ID: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: case POWER_SUPPLY_PROP_CC_STEP: case POWER_SUPPLY_PROP_CC_STEP_SEL: return 1; default: break; Loading Loading @@ -3214,6 +3339,8 @@ static enum power_supply_property fg_psy_props[] = { POWER_SUPPLY_PROP_SOC_REPORTING_READY, POWER_SUPPLY_PROP_DEBUG_BATTERY, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, POWER_SUPPLY_PROP_CC_STEP, POWER_SUPPLY_PROP_CC_STEP_SEL, }; static const struct power_supply_desc fg_psy_desc = { Loading drivers/power/supply/qcom/qpnp-qnovo.c +330 −43 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ #include <linux/of_irq.h> #include <linux/qpnp/qpnp-revid.h> #include <linux/pmic-voter.h> #include <linux/delay.h> #define QNOVO_REVISION1 0x00 #define QNOVO_REVISION2 0x01 Loading Loading @@ -114,6 +115,17 @@ #define OK_TO_QNOVO_VOTER "ok_to_qnovo_voter" #define QNOVO_VOTER "qnovo_voter" #define FG_AVAILABLE_VOTER "FG_AVAILABLE_VOTER" #define QNOVO_OVERALL_VOTER "QNOVO_OVERALL_VOTER" #define QNI_PT_VOTER "QNI_PT_VOTER" #define ESR_VOTER "ESR_VOTER" #define HW_OK_TO_QNOVO_VOTER "HW_OK_TO_QNOVO_VOTER" #define CHG_READY_VOTER "CHG_READY_VOTER" #define USB_READY_VOTER "USB_READY_VOTER" #define DC_READY_VOTER "DC_READY_VOTER" #define PT_RESTART_VOTER "PT_RESTART_VOTER" struct qnovo_dt_props { bool external_rsense; Loading @@ -127,6 +139,10 @@ struct qnovo { struct qnovo_dt_props dt; struct device *dev; struct votable *disable_votable; struct votable *pt_dis_votable; struct votable *not_ok_to_qnovo_votable; struct votable *chg_ready_votable; struct votable *awake_votable; struct class qnovo_class; struct pmic_revid_data *pmic_rev_id; u32 wa_flags; Loading @@ -138,10 +154,18 @@ struct qnovo { s64 v_gain_mega; struct notifier_block nb; struct power_supply *batt_psy; struct power_supply *bms_psy; struct power_supply *usb_psy; struct power_supply *dc_psy; struct work_struct status_change_work; int fv_uV_request; int fcc_uA_request; bool ok_to_qnovo; int usb_present; int dc_present; struct delayed_work usb_debounce_work; struct delayed_work dc_debounce_work; struct delayed_work ptrain_restart_work; }; static int debug_mask; Loading Loading @@ -229,6 +253,39 @@ static bool is_batt_available(struct qnovo *chip) return true; } static bool is_fg_available(struct qnovo *chip) { if (!chip->bms_psy) chip->bms_psy = power_supply_get_by_name("bms"); if (!chip->bms_psy) return false; return true; } static bool is_usb_available(struct qnovo *chip) { if (!chip->usb_psy) chip->usb_psy = power_supply_get_by_name("usb"); if (!chip->usb_psy) return false; return true; } static bool is_dc_available(struct qnovo *chip) { if (!chip->dc_psy) chip->dc_psy = power_supply_get_by_name("dc"); if (!chip->dc_psy) return false; return true; } static int qnovo_batt_psy_update(struct qnovo *chip, bool disable) { union power_supply_propval pval = {0}; Loading Loading @@ -281,10 +338,86 @@ static int qnovo_disable_cb(struct votable *votable, void *data, int disable, return -EINVAL; } /* * fg must be available for enable FG_AVAILABLE_VOTER * won't enable it otherwise */ if (is_fg_available(chip)) power_supply_set_property(chip->bms_psy, POWER_SUPPLY_PROP_CHARGE_QNOVO_ENABLE, &pval); vote(chip->pt_dis_votable, QNOVO_OVERALL_VOTER, disable, 0); rc = qnovo_batt_psy_update(chip, disable); return rc; } static int pt_dis_votable_cb(struct votable *votable, void *data, int disable, const char *client) { struct qnovo *chip = data; int rc; if (disable) { cancel_delayed_work_sync(&chip->ptrain_restart_work); vote(chip->awake_votable, PT_RESTART_VOTER, false, 0); } rc = qnovo_masked_write(chip, QNOVO_PTRAIN_EN, QNOVO_PTRAIN_EN_BIT, (bool)disable ? 0 : QNOVO_PTRAIN_EN_BIT); if (rc < 0) { dev_err(chip->dev, "Couldn't %s pulse train rc=%d\n", (bool)disable ? "disable" : "enable", rc); return rc; } if (!disable) { vote(chip->awake_votable, PT_RESTART_VOTER, true, 0); schedule_delayed_work(&chip->ptrain_restart_work, msecs_to_jiffies(20)); } return 0; } static int not_ok_to_qnovo_cb(struct votable *votable, void *data, int not_ok_to_qnovo, const char *client) { struct qnovo *chip = data; vote(chip->disable_votable, OK_TO_QNOVO_VOTER, not_ok_to_qnovo, 0); if (not_ok_to_qnovo) vote(chip->disable_votable, USER_VOTER, true, 0); kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE); return 0; } static int chg_ready_cb(struct votable *votable, void *data, int ready, const char *client) { struct qnovo *chip = data; vote(chip->not_ok_to_qnovo_votable, CHG_READY_VOTER, !ready, 0); return 0; } static int awake_cb(struct votable *votable, void *data, int awake, const char *client) { struct qnovo *chip = data; if (awake) pm_stay_awake(chip->dev); else pm_relax(chip->dev); return 0; } static int qnovo_parse_dt(struct qnovo *chip) { struct device_node *node = chip->dev->of_node; Loading Loading @@ -626,8 +759,9 @@ static ssize_t ok_to_qnovo_show(struct class *c, struct class_attribute *attr, char *buf) { struct qnovo *chip = container_of(c, struct qnovo, qnovo_class); int val = get_effective_result(chip->not_ok_to_qnovo_votable); return snprintf(buf, PAGE_SIZE, "%d\n", chip->ok_to_qnovo); return snprintf(buf, PAGE_SIZE, "%d\n", !val); } static ssize_t qnovo_enable_show(struct class *c, struct class_attribute *attr, Loading Loading @@ -656,21 +790,10 @@ static ssize_t qnovo_enable_store(struct class *c, struct class_attribute *attr, static ssize_t pt_enable_show(struct class *c, struct class_attribute *attr, char *ubuf) { int i = attr - qnovo_attributes; struct qnovo *chip = container_of(c, struct qnovo, qnovo_class); u8 buf[2] = {0, 0}; u16 regval; int rc; rc = qnovo_read(chip, params[i].start_addr, buf, params[i].num_regs); if (rc < 0) { pr_err("Couldn't read %s rc = %d\n", params[i].name, rc); return -EINVAL; } regval = buf[1] << 8 | buf[0]; int val = get_effective_result(chip->pt_dis_votable); return snprintf(ubuf, PAGE_SIZE, "%d\n", (int)(regval & QNOVO_PTRAIN_EN_BIT)); return snprintf(ubuf, PAGE_SIZE, "%d\n", !val); } static ssize_t pt_enable_store(struct class *c, struct class_attribute *attr, Loading @@ -678,21 +801,12 @@ static ssize_t pt_enable_store(struct class *c, struct class_attribute *attr, { struct qnovo *chip = container_of(c, struct qnovo, qnovo_class); unsigned long val; int rc = 0; if (get_effective_result(chip->disable_votable)) return -EINVAL; if (kstrtoul(ubuf, 0, &val)) return -EINVAL; rc = qnovo_masked_write(chip, QNOVO_PTRAIN_EN, QNOVO_PTRAIN_EN_BIT, (bool)val ? QNOVO_PTRAIN_EN_BIT : 0); if (rc < 0) { dev_err(chip->dev, "Couldn't %s pulse train rc=%d\n", (bool)val ? "enable" : "disable", rc); return rc; } /* val being 0, userspace wishes to disable pt so vote true */ vote(chip->pt_dis_votable, QNI_PT_VOTER, val ? false : true, 0); return count; } Loading Loading @@ -1116,41 +1230,146 @@ static int qnovo_update_status(struct qnovo *chip) { u8 val = 0; int rc; bool ok_to_qnovo; bool changed = false; bool hw_ok_to_qnovo; rc = qnovo_read(chip, QNOVO_ERROR_STS2, &val, 1); if (rc < 0) { pr_err("Couldn't read error sts rc = %d\n", rc); ok_to_qnovo = false; hw_ok_to_qnovo = false; } else { /* * For CV mode keep qnovo enabled, userspace is expected to * disable it after few runs */ ok_to_qnovo = (val == ERR_CV_MODE || val == 0) ? true : false; hw_ok_to_qnovo = (val == ERR_CV_MODE || val == 0) ? true : false; } if (chip->ok_to_qnovo ^ ok_to_qnovo) { vote(chip->not_ok_to_qnovo_votable, HW_OK_TO_QNOVO_VOTER, !hw_ok_to_qnovo, 0); return 0; } vote(chip->disable_votable, OK_TO_QNOVO_VOTER, !ok_to_qnovo, 0); if (!ok_to_qnovo) vote(chip->disable_votable, USER_VOTER, true, 0); static void usb_debounce_work(struct work_struct *work) { struct qnovo *chip = container_of(work, struct qnovo, usb_debounce_work.work); chip->ok_to_qnovo = ok_to_qnovo; changed = true; vote(chip->chg_ready_votable, USB_READY_VOTER, true, 0); vote(chip->awake_votable, USB_READY_VOTER, false, 0); } return changed; static void dc_debounce_work(struct work_struct *work) { struct qnovo *chip = container_of(work, struct qnovo, dc_debounce_work.work); vote(chip->chg_ready_votable, DC_READY_VOTER, true, 0); vote(chip->awake_votable, DC_READY_VOTER, false, 0); } #define DEBOUNCE_MS 15000 /* 15 seconds */ static void status_change_work(struct work_struct *work) { struct qnovo *chip = container_of(work, struct qnovo, status_change_work); union power_supply_propval pval; bool usb_present = false, dc_present = false; int rc; if (qnovo_update_status(chip)) kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE); if (is_fg_available(chip)) vote(chip->disable_votable, FG_AVAILABLE_VOTER, false, 0); if (is_usb_available(chip)) { rc = power_supply_get_property(chip->usb_psy, POWER_SUPPLY_PROP_PRESENT, &pval); usb_present = (rc < 0) ? 0 : pval.intval; } if (chip->usb_present && !usb_present) { /* removal */ chip->usb_present = 0; cancel_delayed_work_sync(&chip->usb_debounce_work); vote(chip->awake_votable, USB_READY_VOTER, false, 0); vote(chip->chg_ready_votable, USB_READY_VOTER, false, 0); } else if (!chip->usb_present && usb_present) { /* insertion */ chip->usb_present = 1; vote(chip->awake_votable, USB_READY_VOTER, true, 0); schedule_delayed_work(&chip->usb_debounce_work, msecs_to_jiffies(DEBOUNCE_MS)); } if (is_dc_available(chip)) { rc = power_supply_get_property(chip->dc_psy, POWER_SUPPLY_PROP_PRESENT, &pval); dc_present = (rc < 0) ? 0 : pval.intval; } if (usb_present) dc_present = 0; if (chip->dc_present && !dc_present) { /* removal */ chip->dc_present = 0; cancel_delayed_work_sync(&chip->dc_debounce_work); vote(chip->awake_votable, DC_READY_VOTER, false, 0); vote(chip->chg_ready_votable, DC_READY_VOTER, false, 0); } else if (!chip->dc_present && dc_present) { /* insertion */ chip->dc_present = 1; vote(chip->awake_votable, DC_READY_VOTER, true, 0); schedule_delayed_work(&chip->dc_debounce_work, msecs_to_jiffies(DEBOUNCE_MS)); } qnovo_update_status(chip); } static void ptrain_restart_work(struct work_struct *work) { struct qnovo *chip = container_of(work, struct qnovo, ptrain_restart_work.work); u8 pt_t1, pt_t2; int rc; rc = qnovo_read(chip, QNOVO_PTTIME_STS, &pt_t1, 1); if (rc < 0) { dev_err(chip->dev, "Couldn't read QNOVO_PTTIME_STS rc = %d\n", rc); goto clean_up; } /* pttime increments every 2 seconds */ msleep(2100); rc = qnovo_read(chip, QNOVO_PTTIME_STS, &pt_t2, 1); if (rc < 0) { dev_err(chip->dev, "Couldn't read QNOVO_PTTIME_STS rc = %d\n", rc); goto clean_up; } if (pt_t1 != pt_t2) goto clean_up; /* Toggle pt enable to restart pulse train */ rc = qnovo_masked_write(chip, QNOVO_PTRAIN_EN, QNOVO_PTRAIN_EN_BIT, 0); if (rc < 0) { dev_err(chip->dev, "Couldn't disable pulse train rc=%d\n", rc); goto clean_up; } msleep(1000); rc = qnovo_masked_write(chip, QNOVO_PTRAIN_EN, QNOVO_PTRAIN_EN_BIT, QNOVO_PTRAIN_EN_BIT); if (rc < 0) { dev_err(chip->dev, "Couldn't enable pulse train rc=%d\n", rc); goto clean_up; } clean_up: vote(chip->awake_votable, PT_RESTART_VOTER, false, 0); } static int qnovo_notifier_call(struct notifier_block *nb, Loading @@ -1162,7 +1381,10 @@ static int qnovo_notifier_call(struct notifier_block *nb, if (ev != PSY_EVENT_PROP_CHANGED) return NOTIFY_OK; if (strcmp(psy->desc->name, "battery") == 0) if (strcmp(psy->desc->name, "battery") == 0 || strcmp(psy->desc->name, "bms") == 0 || strcmp(psy->desc->name, "usb") == 0 || strcmp(psy->desc->name, "dc") == 0) schedule_work(&chip->status_change_work); return NOTIFY_OK; Loading @@ -1171,7 +1393,23 @@ static int qnovo_notifier_call(struct notifier_block *nb, static irqreturn_t handle_ptrain_done(int irq, void *data) { struct qnovo *chip = data; union power_supply_propval pval = {0}; /* * hw resets pt_en bit once ptrain_done triggers. * vote on behalf of QNI to disable it such that * once QNI enables it, the votable state changes * and the callback that sets it is indeed invoked */ vote(chip->pt_dis_votable, QNI_PT_VOTER, true, 0); vote(chip->pt_dis_votable, ESR_VOTER, true, 0); if (is_fg_available(chip)) power_supply_set_property(chip->bms_psy, POWER_SUPPLY_PROP_RESISTANCE, &pval); vote(chip->pt_dis_votable, ESR_VOTER, false, 0); qnovo_update_status(chip); kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE); return IRQ_HANDLED; Loading @@ -1186,6 +1424,11 @@ static int qnovo_hw_init(struct qnovo *chip) u8 val; vote(chip->disable_votable, USER_VOTER, true, 0); vote(chip->disable_votable, FG_AVAILABLE_VOTER, true, 0); vote(chip->pt_dis_votable, QNI_PT_VOTER, true, 0); vote(chip->pt_dis_votable, QNOVO_OVERALL_VOTER, true, 0); vote(chip->pt_dis_votable, ESR_VOTER, false, 0); val = 0; rc = qnovo_write(chip, QNOVO_STRM_CTRL, &val, 1); Loading Loading @@ -1349,12 +1592,45 @@ static int qnovo_probe(struct platform_device *pdev) goto cleanup; } chip->pt_dis_votable = create_votable("QNOVO_PT_DIS", VOTE_SET_ANY, pt_dis_votable_cb, chip); if (IS_ERR(chip->pt_dis_votable)) { rc = PTR_ERR(chip->pt_dis_votable); goto destroy_disable_votable; } chip->not_ok_to_qnovo_votable = create_votable("QNOVO_NOT_OK", VOTE_SET_ANY, not_ok_to_qnovo_cb, chip); if (IS_ERR(chip->not_ok_to_qnovo_votable)) { rc = PTR_ERR(chip->not_ok_to_qnovo_votable); goto destroy_pt_dis_votable; } chip->chg_ready_votable = create_votable("QNOVO_CHG_READY", VOTE_SET_ANY, chg_ready_cb, chip); if (IS_ERR(chip->chg_ready_votable)) { rc = PTR_ERR(chip->chg_ready_votable); goto destroy_not_ok_to_qnovo_votable; } chip->awake_votable = create_votable("QNOVO_AWAKE", VOTE_SET_ANY, awake_cb, chip); if (IS_ERR(chip->awake_votable)) { rc = PTR_ERR(chip->awake_votable); goto destroy_chg_ready_votable; } INIT_WORK(&chip->status_change_work, status_change_work); INIT_DELAYED_WORK(&chip->dc_debounce_work, dc_debounce_work); INIT_DELAYED_WORK(&chip->usb_debounce_work, usb_debounce_work); INIT_DELAYED_WORK(&chip->ptrain_restart_work, ptrain_restart_work); rc = qnovo_hw_init(chip); if (rc < 0) { pr_err("Couldn't initialize hardware rc=%d\n", rc); goto destroy_votable; goto destroy_awake_votable; } rc = qnovo_register_notifier(chip); Loading Loading @@ -1390,7 +1666,15 @@ static int qnovo_probe(struct platform_device *pdev) unreg_notifier: power_supply_unreg_notifier(&chip->nb); destroy_votable: destroy_awake_votable: destroy_votable(chip->awake_votable); destroy_chg_ready_votable: destroy_votable(chip->chg_ready_votable); destroy_not_ok_to_qnovo_votable: destroy_votable(chip->not_ok_to_qnovo_votable); destroy_pt_dis_votable: destroy_votable(chip->pt_dis_votable); destroy_disable_votable: destroy_votable(chip->disable_votable); cleanup: platform_set_drvdata(pdev, NULL); Loading @@ -1403,6 +1687,9 @@ static int qnovo_remove(struct platform_device *pdev) class_unregister(&chip->qnovo_class); power_supply_unreg_notifier(&chip->nb); destroy_votable(chip->chg_ready_votable); destroy_votable(chip->not_ok_to_qnovo_votable); destroy_votable(chip->pt_dis_votable); destroy_votable(chip->disable_votable); platform_set_drvdata(pdev, NULL); return 0; Loading Loading
drivers/power/power_supply_sysfs.c +2 −0 Original line number Diff line number Diff line Loading @@ -297,6 +297,8 @@ static struct device_attribute power_supply_attrs[] = { POWER_SUPPLY_ATTR(hw_current_max), POWER_SUPPLY_ATTR(real_type), POWER_SUPPLY_ATTR(pr_swap), POWER_SUPPLY_ATTR(cc_step), POWER_SUPPLY_ATTR(cc_step_sel), /* Local extensions of type int64_t */ POWER_SUPPLY_ATTR(charge_counter_ext), /* Properties of type `const char *' */ Loading
drivers/power/supply/qcom/fg-core.h +10 −1 Original line number Diff line number Diff line Loading @@ -81,6 +81,8 @@ #define BATT_THERM_NUM_COEFFS 3 #define MAX_CC_STEPS 20 /* Debug flag definitions */ enum fg_debug_flag { FG_IRQ = BIT(0), /* Show interrupts */ Loading Loading @@ -309,11 +311,16 @@ struct fg_irq_info { }; struct fg_circ_buf { int arr[20]; int arr[10]; int size; int head; }; struct fg_cc_step_data { int arr[MAX_CC_STEPS]; int sel; }; struct fg_pt { s32 x; s32 y; Loading Loading @@ -374,6 +381,7 @@ struct fg_chip { struct fg_cyc_ctr_data cyc_ctr; struct notifier_block nb; struct fg_cap_learning cl; struct fg_cc_step_data cc_step; struct mutex bus_lock; struct mutex sram_rw_lock; struct mutex batt_avg_lock; Loading Loading @@ -475,5 +483,6 @@ extern bool is_qnovo_en(struct fg_chip *chip); extern void fg_circ_buf_add(struct fg_circ_buf *, int); extern void fg_circ_buf_clr(struct fg_circ_buf *); extern int fg_circ_buf_avg(struct fg_circ_buf *, int *); extern int fg_circ_buf_median(struct fg_circ_buf *, int *); extern int fg_lerp(const struct fg_pt *, size_t, s32, s32 *); #endif
drivers/power/supply/qcom/fg-util.c +34 −0 Original line number Diff line number Diff line Loading @@ -10,6 +10,7 @@ * GNU General Public License for more details. */ #include <linux/sort.h> #include "fg-core.h" void fg_circ_buf_add(struct fg_circ_buf *buf, int val) Loading Loading @@ -39,6 +40,39 @@ int fg_circ_buf_avg(struct fg_circ_buf *buf, int *avg) return 0; } static int cmp_int(const void *a, const void *b) { return *(int *)a - *(int *)b; } int fg_circ_buf_median(struct fg_circ_buf *buf, int *median) { int *temp; if (buf->size == 0) return -ENODATA; if (buf->size == 1) { *median = buf->arr[0]; return 0; } temp = kmalloc_array(buf->size, sizeof(*temp), GFP_KERNEL); if (!temp) return -ENOMEM; memcpy(temp, buf->arr, buf->size * sizeof(*temp)); sort(temp, buf->size, sizeof(*temp), cmp_int, NULL); if (buf->size % 2) *median = temp[buf->size / 2]; else *median = (temp[buf->size / 2 - 1] + temp[buf->size / 2]) / 2; kfree(temp); return 0; } int fg_lerp(const struct fg_pt *pts, size_t tablesize, s32 input, s32 *output) { int i; Loading
drivers/power/supply/qcom/qpnp-fg-gen3.c +127 −0 Original line number Diff line number Diff line Loading @@ -75,6 +75,8 @@ #define ESR_TIMER_CHG_MAX_OFFSET 0 #define ESR_TIMER_CHG_INIT_WORD 18 #define ESR_TIMER_CHG_INIT_OFFSET 2 #define ESR_EXTRACTION_ENABLE_WORD 19 #define ESR_EXTRACTION_ENABLE_OFFSET 0 #define PROFILE_LOAD_WORD 24 #define PROFILE_LOAD_OFFSET 0 #define ESR_RSLOW_DISCHG_WORD 34 Loading Loading @@ -3033,6 +3035,89 @@ static int fg_esr_validate(struct fg_chip *chip) return 0; } static int fg_force_esr_meas(struct fg_chip *chip) { int rc; int esr_uohms; /* force esr extraction enable */ rc = fg_sram_masked_write(chip, ESR_EXTRACTION_ENABLE_WORD, ESR_EXTRACTION_ENABLE_OFFSET, BIT(0), BIT(0), FG_IMA_DEFAULT); if (rc < 0) { pr_err("failed to enable esr extn rc=%d\n", rc); return rc; } rc = fg_masked_write(chip, BATT_INFO_QNOVO_CFG(chip), LD_REG_CTRL_BIT, 0); if (rc < 0) { pr_err("Error in configuring qnovo_cfg rc=%d\n", rc); return rc; } rc = fg_masked_write(chip, BATT_INFO_TM_MISC1(chip), ESR_REQ_CTL_BIT | ESR_REQ_CTL_EN_BIT, ESR_REQ_CTL_BIT | ESR_REQ_CTL_EN_BIT); if (rc < 0) { pr_err("Error in configuring force ESR rc=%d\n", rc); return rc; } /* wait 1.5 seconds for hw to measure ESR */ msleep(1500); rc = fg_masked_write(chip, BATT_INFO_TM_MISC1(chip), ESR_REQ_CTL_BIT | ESR_REQ_CTL_EN_BIT, 0); if (rc < 0) { pr_err("Error in restoring force ESR rc=%d\n", rc); return rc; } rc = fg_masked_write(chip, BATT_INFO_QNOVO_CFG(chip), LD_REG_CTRL_BIT, LD_REG_CTRL_BIT); if (rc < 0) { pr_err("Error in restoring qnovo_cfg rc=%d\n", rc); return rc; } /* force esr extraction disable */ rc = fg_sram_masked_write(chip, ESR_EXTRACTION_ENABLE_WORD, ESR_EXTRACTION_ENABLE_OFFSET, BIT(0), 0, FG_IMA_DEFAULT); if (rc < 0) { pr_err("failed to disable esr extn rc=%d\n", rc); return rc; } fg_get_battery_resistance(chip, &esr_uohms); fg_dbg(chip, FG_STATUS, "ESR uohms = %d\n", esr_uohms); return rc; } static int fg_prepare_for_qnovo(struct fg_chip *chip, int qnovo_enable) { int rc; /* force esr extraction disable when qnovo enables */ rc = fg_sram_masked_write(chip, ESR_EXTRACTION_ENABLE_WORD, ESR_EXTRACTION_ENABLE_OFFSET, BIT(0), qnovo_enable ? 0 : BIT(0), FG_IMA_DEFAULT); if (rc < 0) pr_err("Error in configuring esr extraction rc=%d\n", rc); rc = fg_masked_write(chip, BATT_INFO_QNOVO_CFG(chip), LD_REG_CTRL_BIT, qnovo_enable ? LD_REG_CTRL_BIT : 0); if (rc < 0) { pr_err("Error in configuring qnovo_cfg rc=%d\n", rc); return rc; } fg_dbg(chip, FG_STATUS, "Prepared for Qnovo\n"); return 0; } /* PSY CALLBACKS STAY HERE */ static int fg_psy_get_property(struct power_supply *psy, Loading Loading @@ -3109,6 +3194,19 @@ static int fg_psy_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: rc = fg_get_sram_prop(chip, FG_SRAM_VBATT_FULL, &pval->intval); break; case POWER_SUPPLY_PROP_CC_STEP: if ((chip->cc_step.sel >= 0) && (chip->cc_step.sel < MAX_CC_STEPS)) { pval->intval = chip->cc_step.arr[chip->cc_step.sel]; } else { pr_err("cc_step_sel is out of bounds [0, %d]\n", chip->cc_step.sel); return -EINVAL; } break; case POWER_SUPPLY_PROP_CC_STEP_SEL: pval->intval = chip->cc_step.sel; break; default: pr_err("unsupported property %d\n", psp); rc = -EINVAL; Loading Loading @@ -3141,6 +3239,31 @@ static int fg_psy_set_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: rc = fg_set_constant_chg_voltage(chip, pval->intval); break; case POWER_SUPPLY_PROP_RESISTANCE: rc = fg_force_esr_meas(chip); break; case POWER_SUPPLY_PROP_CHARGE_QNOVO_ENABLE: rc = fg_prepare_for_qnovo(chip, pval->intval); break; case POWER_SUPPLY_PROP_CC_STEP: if ((chip->cc_step.sel >= 0) && (chip->cc_step.sel < MAX_CC_STEPS)) { chip->cc_step.arr[chip->cc_step.sel] = pval->intval; } else { pr_err("cc_step_sel is out of bounds [0, %d]\n", chip->cc_step.sel); return -EINVAL; } break; case POWER_SUPPLY_PROP_CC_STEP_SEL: if ((pval->intval >= 0) && (pval->intval < MAX_CC_STEPS)) { chip->cc_step.sel = pval->intval; } else { pr_err("cc_step_sel is out of bounds [0, %d]\n", pval->intval); return -EINVAL; } break; default: break; } Loading @@ -3154,6 +3277,8 @@ static int fg_property_is_writeable(struct power_supply *psy, switch (psp) { case POWER_SUPPLY_PROP_CYCLE_COUNT_ID: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: case POWER_SUPPLY_PROP_CC_STEP: case POWER_SUPPLY_PROP_CC_STEP_SEL: return 1; default: break; Loading Loading @@ -3214,6 +3339,8 @@ static enum power_supply_property fg_psy_props[] = { POWER_SUPPLY_PROP_SOC_REPORTING_READY, POWER_SUPPLY_PROP_DEBUG_BATTERY, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, POWER_SUPPLY_PROP_CC_STEP, POWER_SUPPLY_PROP_CC_STEP_SEL, }; static const struct power_supply_desc fg_psy_desc = { Loading
drivers/power/supply/qcom/qpnp-qnovo.c +330 −43 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ #include <linux/of_irq.h> #include <linux/qpnp/qpnp-revid.h> #include <linux/pmic-voter.h> #include <linux/delay.h> #define QNOVO_REVISION1 0x00 #define QNOVO_REVISION2 0x01 Loading Loading @@ -114,6 +115,17 @@ #define OK_TO_QNOVO_VOTER "ok_to_qnovo_voter" #define QNOVO_VOTER "qnovo_voter" #define FG_AVAILABLE_VOTER "FG_AVAILABLE_VOTER" #define QNOVO_OVERALL_VOTER "QNOVO_OVERALL_VOTER" #define QNI_PT_VOTER "QNI_PT_VOTER" #define ESR_VOTER "ESR_VOTER" #define HW_OK_TO_QNOVO_VOTER "HW_OK_TO_QNOVO_VOTER" #define CHG_READY_VOTER "CHG_READY_VOTER" #define USB_READY_VOTER "USB_READY_VOTER" #define DC_READY_VOTER "DC_READY_VOTER" #define PT_RESTART_VOTER "PT_RESTART_VOTER" struct qnovo_dt_props { bool external_rsense; Loading @@ -127,6 +139,10 @@ struct qnovo { struct qnovo_dt_props dt; struct device *dev; struct votable *disable_votable; struct votable *pt_dis_votable; struct votable *not_ok_to_qnovo_votable; struct votable *chg_ready_votable; struct votable *awake_votable; struct class qnovo_class; struct pmic_revid_data *pmic_rev_id; u32 wa_flags; Loading @@ -138,10 +154,18 @@ struct qnovo { s64 v_gain_mega; struct notifier_block nb; struct power_supply *batt_psy; struct power_supply *bms_psy; struct power_supply *usb_psy; struct power_supply *dc_psy; struct work_struct status_change_work; int fv_uV_request; int fcc_uA_request; bool ok_to_qnovo; int usb_present; int dc_present; struct delayed_work usb_debounce_work; struct delayed_work dc_debounce_work; struct delayed_work ptrain_restart_work; }; static int debug_mask; Loading Loading @@ -229,6 +253,39 @@ static bool is_batt_available(struct qnovo *chip) return true; } static bool is_fg_available(struct qnovo *chip) { if (!chip->bms_psy) chip->bms_psy = power_supply_get_by_name("bms"); if (!chip->bms_psy) return false; return true; } static bool is_usb_available(struct qnovo *chip) { if (!chip->usb_psy) chip->usb_psy = power_supply_get_by_name("usb"); if (!chip->usb_psy) return false; return true; } static bool is_dc_available(struct qnovo *chip) { if (!chip->dc_psy) chip->dc_psy = power_supply_get_by_name("dc"); if (!chip->dc_psy) return false; return true; } static int qnovo_batt_psy_update(struct qnovo *chip, bool disable) { union power_supply_propval pval = {0}; Loading Loading @@ -281,10 +338,86 @@ static int qnovo_disable_cb(struct votable *votable, void *data, int disable, return -EINVAL; } /* * fg must be available for enable FG_AVAILABLE_VOTER * won't enable it otherwise */ if (is_fg_available(chip)) power_supply_set_property(chip->bms_psy, POWER_SUPPLY_PROP_CHARGE_QNOVO_ENABLE, &pval); vote(chip->pt_dis_votable, QNOVO_OVERALL_VOTER, disable, 0); rc = qnovo_batt_psy_update(chip, disable); return rc; } static int pt_dis_votable_cb(struct votable *votable, void *data, int disable, const char *client) { struct qnovo *chip = data; int rc; if (disable) { cancel_delayed_work_sync(&chip->ptrain_restart_work); vote(chip->awake_votable, PT_RESTART_VOTER, false, 0); } rc = qnovo_masked_write(chip, QNOVO_PTRAIN_EN, QNOVO_PTRAIN_EN_BIT, (bool)disable ? 0 : QNOVO_PTRAIN_EN_BIT); if (rc < 0) { dev_err(chip->dev, "Couldn't %s pulse train rc=%d\n", (bool)disable ? "disable" : "enable", rc); return rc; } if (!disable) { vote(chip->awake_votable, PT_RESTART_VOTER, true, 0); schedule_delayed_work(&chip->ptrain_restart_work, msecs_to_jiffies(20)); } return 0; } static int not_ok_to_qnovo_cb(struct votable *votable, void *data, int not_ok_to_qnovo, const char *client) { struct qnovo *chip = data; vote(chip->disable_votable, OK_TO_QNOVO_VOTER, not_ok_to_qnovo, 0); if (not_ok_to_qnovo) vote(chip->disable_votable, USER_VOTER, true, 0); kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE); return 0; } static int chg_ready_cb(struct votable *votable, void *data, int ready, const char *client) { struct qnovo *chip = data; vote(chip->not_ok_to_qnovo_votable, CHG_READY_VOTER, !ready, 0); return 0; } static int awake_cb(struct votable *votable, void *data, int awake, const char *client) { struct qnovo *chip = data; if (awake) pm_stay_awake(chip->dev); else pm_relax(chip->dev); return 0; } static int qnovo_parse_dt(struct qnovo *chip) { struct device_node *node = chip->dev->of_node; Loading Loading @@ -626,8 +759,9 @@ static ssize_t ok_to_qnovo_show(struct class *c, struct class_attribute *attr, char *buf) { struct qnovo *chip = container_of(c, struct qnovo, qnovo_class); int val = get_effective_result(chip->not_ok_to_qnovo_votable); return snprintf(buf, PAGE_SIZE, "%d\n", chip->ok_to_qnovo); return snprintf(buf, PAGE_SIZE, "%d\n", !val); } static ssize_t qnovo_enable_show(struct class *c, struct class_attribute *attr, Loading Loading @@ -656,21 +790,10 @@ static ssize_t qnovo_enable_store(struct class *c, struct class_attribute *attr, static ssize_t pt_enable_show(struct class *c, struct class_attribute *attr, char *ubuf) { int i = attr - qnovo_attributes; struct qnovo *chip = container_of(c, struct qnovo, qnovo_class); u8 buf[2] = {0, 0}; u16 regval; int rc; rc = qnovo_read(chip, params[i].start_addr, buf, params[i].num_regs); if (rc < 0) { pr_err("Couldn't read %s rc = %d\n", params[i].name, rc); return -EINVAL; } regval = buf[1] << 8 | buf[0]; int val = get_effective_result(chip->pt_dis_votable); return snprintf(ubuf, PAGE_SIZE, "%d\n", (int)(regval & QNOVO_PTRAIN_EN_BIT)); return snprintf(ubuf, PAGE_SIZE, "%d\n", !val); } static ssize_t pt_enable_store(struct class *c, struct class_attribute *attr, Loading @@ -678,21 +801,12 @@ static ssize_t pt_enable_store(struct class *c, struct class_attribute *attr, { struct qnovo *chip = container_of(c, struct qnovo, qnovo_class); unsigned long val; int rc = 0; if (get_effective_result(chip->disable_votable)) return -EINVAL; if (kstrtoul(ubuf, 0, &val)) return -EINVAL; rc = qnovo_masked_write(chip, QNOVO_PTRAIN_EN, QNOVO_PTRAIN_EN_BIT, (bool)val ? QNOVO_PTRAIN_EN_BIT : 0); if (rc < 0) { dev_err(chip->dev, "Couldn't %s pulse train rc=%d\n", (bool)val ? "enable" : "disable", rc); return rc; } /* val being 0, userspace wishes to disable pt so vote true */ vote(chip->pt_dis_votable, QNI_PT_VOTER, val ? false : true, 0); return count; } Loading Loading @@ -1116,41 +1230,146 @@ static int qnovo_update_status(struct qnovo *chip) { u8 val = 0; int rc; bool ok_to_qnovo; bool changed = false; bool hw_ok_to_qnovo; rc = qnovo_read(chip, QNOVO_ERROR_STS2, &val, 1); if (rc < 0) { pr_err("Couldn't read error sts rc = %d\n", rc); ok_to_qnovo = false; hw_ok_to_qnovo = false; } else { /* * For CV mode keep qnovo enabled, userspace is expected to * disable it after few runs */ ok_to_qnovo = (val == ERR_CV_MODE || val == 0) ? true : false; hw_ok_to_qnovo = (val == ERR_CV_MODE || val == 0) ? true : false; } if (chip->ok_to_qnovo ^ ok_to_qnovo) { vote(chip->not_ok_to_qnovo_votable, HW_OK_TO_QNOVO_VOTER, !hw_ok_to_qnovo, 0); return 0; } vote(chip->disable_votable, OK_TO_QNOVO_VOTER, !ok_to_qnovo, 0); if (!ok_to_qnovo) vote(chip->disable_votable, USER_VOTER, true, 0); static void usb_debounce_work(struct work_struct *work) { struct qnovo *chip = container_of(work, struct qnovo, usb_debounce_work.work); chip->ok_to_qnovo = ok_to_qnovo; changed = true; vote(chip->chg_ready_votable, USB_READY_VOTER, true, 0); vote(chip->awake_votable, USB_READY_VOTER, false, 0); } return changed; static void dc_debounce_work(struct work_struct *work) { struct qnovo *chip = container_of(work, struct qnovo, dc_debounce_work.work); vote(chip->chg_ready_votable, DC_READY_VOTER, true, 0); vote(chip->awake_votable, DC_READY_VOTER, false, 0); } #define DEBOUNCE_MS 15000 /* 15 seconds */ static void status_change_work(struct work_struct *work) { struct qnovo *chip = container_of(work, struct qnovo, status_change_work); union power_supply_propval pval; bool usb_present = false, dc_present = false; int rc; if (qnovo_update_status(chip)) kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE); if (is_fg_available(chip)) vote(chip->disable_votable, FG_AVAILABLE_VOTER, false, 0); if (is_usb_available(chip)) { rc = power_supply_get_property(chip->usb_psy, POWER_SUPPLY_PROP_PRESENT, &pval); usb_present = (rc < 0) ? 0 : pval.intval; } if (chip->usb_present && !usb_present) { /* removal */ chip->usb_present = 0; cancel_delayed_work_sync(&chip->usb_debounce_work); vote(chip->awake_votable, USB_READY_VOTER, false, 0); vote(chip->chg_ready_votable, USB_READY_VOTER, false, 0); } else if (!chip->usb_present && usb_present) { /* insertion */ chip->usb_present = 1; vote(chip->awake_votable, USB_READY_VOTER, true, 0); schedule_delayed_work(&chip->usb_debounce_work, msecs_to_jiffies(DEBOUNCE_MS)); } if (is_dc_available(chip)) { rc = power_supply_get_property(chip->dc_psy, POWER_SUPPLY_PROP_PRESENT, &pval); dc_present = (rc < 0) ? 0 : pval.intval; } if (usb_present) dc_present = 0; if (chip->dc_present && !dc_present) { /* removal */ chip->dc_present = 0; cancel_delayed_work_sync(&chip->dc_debounce_work); vote(chip->awake_votable, DC_READY_VOTER, false, 0); vote(chip->chg_ready_votable, DC_READY_VOTER, false, 0); } else if (!chip->dc_present && dc_present) { /* insertion */ chip->dc_present = 1; vote(chip->awake_votable, DC_READY_VOTER, true, 0); schedule_delayed_work(&chip->dc_debounce_work, msecs_to_jiffies(DEBOUNCE_MS)); } qnovo_update_status(chip); } static void ptrain_restart_work(struct work_struct *work) { struct qnovo *chip = container_of(work, struct qnovo, ptrain_restart_work.work); u8 pt_t1, pt_t2; int rc; rc = qnovo_read(chip, QNOVO_PTTIME_STS, &pt_t1, 1); if (rc < 0) { dev_err(chip->dev, "Couldn't read QNOVO_PTTIME_STS rc = %d\n", rc); goto clean_up; } /* pttime increments every 2 seconds */ msleep(2100); rc = qnovo_read(chip, QNOVO_PTTIME_STS, &pt_t2, 1); if (rc < 0) { dev_err(chip->dev, "Couldn't read QNOVO_PTTIME_STS rc = %d\n", rc); goto clean_up; } if (pt_t1 != pt_t2) goto clean_up; /* Toggle pt enable to restart pulse train */ rc = qnovo_masked_write(chip, QNOVO_PTRAIN_EN, QNOVO_PTRAIN_EN_BIT, 0); if (rc < 0) { dev_err(chip->dev, "Couldn't disable pulse train rc=%d\n", rc); goto clean_up; } msleep(1000); rc = qnovo_masked_write(chip, QNOVO_PTRAIN_EN, QNOVO_PTRAIN_EN_BIT, QNOVO_PTRAIN_EN_BIT); if (rc < 0) { dev_err(chip->dev, "Couldn't enable pulse train rc=%d\n", rc); goto clean_up; } clean_up: vote(chip->awake_votable, PT_RESTART_VOTER, false, 0); } static int qnovo_notifier_call(struct notifier_block *nb, Loading @@ -1162,7 +1381,10 @@ static int qnovo_notifier_call(struct notifier_block *nb, if (ev != PSY_EVENT_PROP_CHANGED) return NOTIFY_OK; if (strcmp(psy->desc->name, "battery") == 0) if (strcmp(psy->desc->name, "battery") == 0 || strcmp(psy->desc->name, "bms") == 0 || strcmp(psy->desc->name, "usb") == 0 || strcmp(psy->desc->name, "dc") == 0) schedule_work(&chip->status_change_work); return NOTIFY_OK; Loading @@ -1171,7 +1393,23 @@ static int qnovo_notifier_call(struct notifier_block *nb, static irqreturn_t handle_ptrain_done(int irq, void *data) { struct qnovo *chip = data; union power_supply_propval pval = {0}; /* * hw resets pt_en bit once ptrain_done triggers. * vote on behalf of QNI to disable it such that * once QNI enables it, the votable state changes * and the callback that sets it is indeed invoked */ vote(chip->pt_dis_votable, QNI_PT_VOTER, true, 0); vote(chip->pt_dis_votable, ESR_VOTER, true, 0); if (is_fg_available(chip)) power_supply_set_property(chip->bms_psy, POWER_SUPPLY_PROP_RESISTANCE, &pval); vote(chip->pt_dis_votable, ESR_VOTER, false, 0); qnovo_update_status(chip); kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE); return IRQ_HANDLED; Loading @@ -1186,6 +1424,11 @@ static int qnovo_hw_init(struct qnovo *chip) u8 val; vote(chip->disable_votable, USER_VOTER, true, 0); vote(chip->disable_votable, FG_AVAILABLE_VOTER, true, 0); vote(chip->pt_dis_votable, QNI_PT_VOTER, true, 0); vote(chip->pt_dis_votable, QNOVO_OVERALL_VOTER, true, 0); vote(chip->pt_dis_votable, ESR_VOTER, false, 0); val = 0; rc = qnovo_write(chip, QNOVO_STRM_CTRL, &val, 1); Loading Loading @@ -1349,12 +1592,45 @@ static int qnovo_probe(struct platform_device *pdev) goto cleanup; } chip->pt_dis_votable = create_votable("QNOVO_PT_DIS", VOTE_SET_ANY, pt_dis_votable_cb, chip); if (IS_ERR(chip->pt_dis_votable)) { rc = PTR_ERR(chip->pt_dis_votable); goto destroy_disable_votable; } chip->not_ok_to_qnovo_votable = create_votable("QNOVO_NOT_OK", VOTE_SET_ANY, not_ok_to_qnovo_cb, chip); if (IS_ERR(chip->not_ok_to_qnovo_votable)) { rc = PTR_ERR(chip->not_ok_to_qnovo_votable); goto destroy_pt_dis_votable; } chip->chg_ready_votable = create_votable("QNOVO_CHG_READY", VOTE_SET_ANY, chg_ready_cb, chip); if (IS_ERR(chip->chg_ready_votable)) { rc = PTR_ERR(chip->chg_ready_votable); goto destroy_not_ok_to_qnovo_votable; } chip->awake_votable = create_votable("QNOVO_AWAKE", VOTE_SET_ANY, awake_cb, chip); if (IS_ERR(chip->awake_votable)) { rc = PTR_ERR(chip->awake_votable); goto destroy_chg_ready_votable; } INIT_WORK(&chip->status_change_work, status_change_work); INIT_DELAYED_WORK(&chip->dc_debounce_work, dc_debounce_work); INIT_DELAYED_WORK(&chip->usb_debounce_work, usb_debounce_work); INIT_DELAYED_WORK(&chip->ptrain_restart_work, ptrain_restart_work); rc = qnovo_hw_init(chip); if (rc < 0) { pr_err("Couldn't initialize hardware rc=%d\n", rc); goto destroy_votable; goto destroy_awake_votable; } rc = qnovo_register_notifier(chip); Loading Loading @@ -1390,7 +1666,15 @@ static int qnovo_probe(struct platform_device *pdev) unreg_notifier: power_supply_unreg_notifier(&chip->nb); destroy_votable: destroy_awake_votable: destroy_votable(chip->awake_votable); destroy_chg_ready_votable: destroy_votable(chip->chg_ready_votable); destroy_not_ok_to_qnovo_votable: destroy_votable(chip->not_ok_to_qnovo_votable); destroy_pt_dis_votable: destroy_votable(chip->pt_dis_votable); destroy_disable_votable: destroy_votable(chip->disable_votable); cleanup: platform_set_drvdata(pdev, NULL); Loading @@ -1403,6 +1687,9 @@ static int qnovo_remove(struct platform_device *pdev) class_unregister(&chip->qnovo_class); power_supply_unreg_notifier(&chip->nb); destroy_votable(chip->chg_ready_votable); destroy_votable(chip->not_ok_to_qnovo_votable); destroy_votable(chip->pt_dis_votable); destroy_votable(chip->disable_votable); platform_set_drvdata(pdev, NULL); return 0; Loading