Merge "power: fg-util: add median filter for circular buffer"
This commit is contained in:
commit
0e4e7eab42
7 changed files with 523 additions and 51 deletions
|
@ -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 *' */
|
||||
|
|
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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 = {
|
||||
|
|
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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};
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
int val = get_effective_result(chip->pt_dis_votable);
|
||||
|
||||
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];
|
||||
|
||||
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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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->disable_votable, OK_TO_QNOVO_VOTER, !ok_to_qnovo, 0);
|
||||
if (!ok_to_qnovo)
|
||||
vote(chip->disable_votable, USER_VOTER, true, 0);
|
||||
|
||||
chip->ok_to_qnovo = ok_to_qnovo;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
vote(chip->not_ok_to_qnovo_votable, HW_OK_TO_QNOVO_VOTER,
|
||||
!hw_ok_to_qnovo, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void usb_debounce_work(struct work_struct *work)
|
||||
{
|
||||
struct qnovo *chip = container_of(work,
|
||||
struct qnovo, usb_debounce_work.work);
|
||||
|
||||
vote(chip->chg_ready_votable, USB_READY_VOTER, true, 0);
|
||||
vote(chip->awake_votable, USB_READY_VOTER, false, 0);
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
|
|
@ -1571,8 +1571,8 @@ int smblib_get_prop_batt_status(struct smb_charger *chg,
|
|||
union power_supply_propval *val)
|
||||
{
|
||||
union power_supply_propval pval = {0, };
|
||||
bool usb_online, dc_online;
|
||||
u8 stat;
|
||||
bool usb_online, dc_online, qnovo_en;
|
||||
u8 stat, pt_en_cmd;
|
||||
int rc;
|
||||
|
||||
rc = smblib_get_prop_usb_online(chg, &pval);
|
||||
|
@ -1640,11 +1640,22 @@ int smblib_get_prop_batt_status(struct smb_charger *chg,
|
|||
smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_2 rc=%d\n",
|
||||
rc);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
stat &= ENABLE_TRICKLE_BIT | ENABLE_PRE_CHARGING_BIT |
|
||||
ENABLE_FAST_CHARGING_BIT | ENABLE_FULLON_MODE_BIT;
|
||||
if (!stat)
|
||||
|
||||
rc = smblib_read(chg, QNOVO_PT_ENABLE_CMD_REG, &pt_en_cmd);
|
||||
if (rc < 0) {
|
||||
smblib_err(chg, "Couldn't read QNOVO_PT_ENABLE_CMD_REG rc=%d\n",
|
||||
rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
qnovo_en = (bool)(pt_en_cmd & QNOVO_PT_ENABLE_CMD_BIT);
|
||||
|
||||
/* ignore stat7 when qnovo is enabled */
|
||||
if (!qnovo_en && !stat)
|
||||
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -249,6 +249,8 @@ enum power_supply_property {
|
|||
POWER_SUPPLY_PROP_HW_CURRENT_MAX,
|
||||
POWER_SUPPLY_PROP_REAL_TYPE,
|
||||
POWER_SUPPLY_PROP_PR_SWAP,
|
||||
POWER_SUPPLY_PROP_CC_STEP,
|
||||
POWER_SUPPLY_PROP_CC_STEP_SEL,
|
||||
/* Local extensions of type int64_t */
|
||||
POWER_SUPPLY_PROP_CHARGE_COUNTER_EXT,
|
||||
/* Properties of type `const char *' */
|
||||
|
|
Loading…
Add table
Reference in a new issue