qpnp-qnovo: add debounce for charger
The request is to delay ok_to_qnovo for 15 seconds after a charger is inserted. To achive that: Make ok_to_qnovo variable as a votable that can be voted on by A. HW error conditions B. Charger is ready after debounce Moreover since there could be two chargers -usb and dc, create another votable to track their individual debounce states. The result of this votable will feed in to ok_to_qnovo votable as branch B above. Also since we need to stay awake for the debounce time, create an awake_votable and vote on it while debounce on either usb or dc is in progress. Change-Id: I32dc07ba16ef0515a9683f2702d317c39baa2eba Signed-off-by: Abhijeet Dharmapurikar <adharmap@codeaurora.org>
This commit is contained in:
parent
354e7f16bd
commit
cbff20cc9a
1 changed files with 186 additions and 20 deletions
|
@ -119,6 +119,11 @@
|
||||||
#define QNI_PT_VOTER "QNI_PT_VOTER"
|
#define QNI_PT_VOTER "QNI_PT_VOTER"
|
||||||
#define ESR_VOTER "ESR_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"
|
||||||
|
|
||||||
struct qnovo_dt_props {
|
struct qnovo_dt_props {
|
||||||
bool external_rsense;
|
bool external_rsense;
|
||||||
struct device_node *revid_dev_node;
|
struct device_node *revid_dev_node;
|
||||||
|
@ -132,6 +137,9 @@ struct qnovo {
|
||||||
struct device *dev;
|
struct device *dev;
|
||||||
struct votable *disable_votable;
|
struct votable *disable_votable;
|
||||||
struct votable *pt_dis_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 class qnovo_class;
|
||||||
struct pmic_revid_data *pmic_rev_id;
|
struct pmic_revid_data *pmic_rev_id;
|
||||||
u32 wa_flags;
|
u32 wa_flags;
|
||||||
|
@ -144,10 +152,15 @@ struct qnovo {
|
||||||
struct notifier_block nb;
|
struct notifier_block nb;
|
||||||
struct power_supply *batt_psy;
|
struct power_supply *batt_psy;
|
||||||
struct power_supply *bms_psy;
|
struct power_supply *bms_psy;
|
||||||
|
struct power_supply *usb_psy;
|
||||||
|
struct power_supply *dc_psy;
|
||||||
struct work_struct status_change_work;
|
struct work_struct status_change_work;
|
||||||
int fv_uV_request;
|
int fv_uV_request;
|
||||||
int fcc_uA_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;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int debug_mask;
|
static int debug_mask;
|
||||||
|
@ -246,6 +259,28 @@ static bool is_fg_available(struct qnovo *chip)
|
||||||
return true;
|
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)
|
static int qnovo_batt_psy_update(struct qnovo *chip, bool disable)
|
||||||
{
|
{
|
||||||
union power_supply_propval pval = {0};
|
union power_supply_propval pval = {0};
|
||||||
|
@ -330,6 +365,43 @@ static int pt_dis_votable_cb(struct votable *votable, void *data, int disable,
|
||||||
return 0;
|
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)
|
static int qnovo_parse_dt(struct qnovo *chip)
|
||||||
{
|
{
|
||||||
struct device_node *node = chip->dev->of_node;
|
struct device_node *node = chip->dev->of_node;
|
||||||
|
@ -671,8 +743,9 @@ static ssize_t ok_to_qnovo_show(struct class *c, struct class_attribute *attr,
|
||||||
char *buf)
|
char *buf)
|
||||||
{
|
{
|
||||||
struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
|
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,
|
static ssize_t qnovo_enable_show(struct class *c, struct class_attribute *attr,
|
||||||
|
@ -1141,43 +1214,101 @@ static int qnovo_update_status(struct qnovo *chip)
|
||||||
{
|
{
|
||||||
u8 val = 0;
|
u8 val = 0;
|
||||||
int rc;
|
int rc;
|
||||||
bool ok_to_qnovo;
|
bool hw_ok_to_qnovo;
|
||||||
bool changed = false;
|
|
||||||
|
|
||||||
rc = qnovo_read(chip, QNOVO_ERROR_STS2, &val, 1);
|
rc = qnovo_read(chip, QNOVO_ERROR_STS2, &val, 1);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
pr_err("Couldn't read error sts rc = %d\n", rc);
|
pr_err("Couldn't read error sts rc = %d\n", rc);
|
||||||
ok_to_qnovo = false;
|
hw_ok_to_qnovo = false;
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
* For CV mode keep qnovo enabled, userspace is expected to
|
* For CV mode keep qnovo enabled, userspace is expected to
|
||||||
* disable it after few runs
|
* 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,
|
||||||
vote(chip->disable_votable, OK_TO_QNOVO_VOTER, !ok_to_qnovo, 0);
|
!hw_ok_to_qnovo, 0);
|
||||||
if (!ok_to_qnovo)
|
return 0;
|
||||||
vote(chip->disable_votable, USER_VOTER, true, 0);
|
|
||||||
|
|
||||||
chip->ok_to_qnovo = ok_to_qnovo;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return changed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
static void status_change_work(struct work_struct *work)
|
||||||
{
|
{
|
||||||
struct qnovo *chip = container_of(work,
|
struct qnovo *chip = container_of(work,
|
||||||
struct qnovo, status_change_work);
|
struct qnovo, status_change_work);
|
||||||
|
union power_supply_propval pval;
|
||||||
|
bool usb_present = false, dc_present = false;
|
||||||
|
int rc;
|
||||||
|
|
||||||
if (is_fg_available(chip))
|
if (is_fg_available(chip))
|
||||||
vote(chip->disable_votable, FG_AVAILABLE_VOTER, false, 0);
|
vote(chip->disable_votable, FG_AVAILABLE_VOTER, false, 0);
|
||||||
|
|
||||||
if (qnovo_update_status(chip))
|
if (is_usb_available(chip)) {
|
||||||
kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE);
|
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 int qnovo_notifier_call(struct notifier_block *nb,
|
static int qnovo_notifier_call(struct notifier_block *nb,
|
||||||
|
@ -1190,7 +1321,9 @@ static int qnovo_notifier_call(struct notifier_block *nb,
|
||||||
return NOTIFY_OK;
|
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, "bms") == 0
|
||||||
|
|| strcmp(psy->desc->name, "usb") == 0
|
||||||
|
|| strcmp(psy->desc->name, "dc") == 0)
|
||||||
schedule_work(&chip->status_change_work);
|
schedule_work(&chip->status_change_work);
|
||||||
|
|
||||||
return NOTIFY_OK;
|
return NOTIFY_OK;
|
||||||
|
@ -1405,12 +1538,37 @@ static int qnovo_probe(struct platform_device *pdev)
|
||||||
goto destroy_disable_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_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);
|
||||||
|
|
||||||
rc = qnovo_hw_init(chip);
|
rc = qnovo_hw_init(chip);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
pr_err("Couldn't initialize hardware rc=%d\n", rc);
|
pr_err("Couldn't initialize hardware rc=%d\n", rc);
|
||||||
goto destroy_pt_dis_votable;
|
goto destroy_awake_votable;
|
||||||
}
|
}
|
||||||
|
|
||||||
rc = qnovo_register_notifier(chip);
|
rc = qnovo_register_notifier(chip);
|
||||||
|
@ -1446,6 +1604,12 @@ static int qnovo_probe(struct platform_device *pdev)
|
||||||
|
|
||||||
unreg_notifier:
|
unreg_notifier:
|
||||||
power_supply_unreg_notifier(&chip->nb);
|
power_supply_unreg_notifier(&chip->nb);
|
||||||
|
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_pt_dis_votable:
|
||||||
destroy_votable(chip->pt_dis_votable);
|
destroy_votable(chip->pt_dis_votable);
|
||||||
destroy_disable_votable:
|
destroy_disable_votable:
|
||||||
|
@ -1461,6 +1625,8 @@ static int qnovo_remove(struct platform_device *pdev)
|
||||||
|
|
||||||
class_unregister(&chip->qnovo_class);
|
class_unregister(&chip->qnovo_class);
|
||||||
power_supply_unreg_notifier(&chip->nb);
|
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->pt_dis_votable);
|
||||||
destroy_votable(chip->disable_votable);
|
destroy_votable(chip->disable_votable);
|
||||||
platform_set_drvdata(pdev, NULL);
|
platform_set_drvdata(pdev, NULL);
|
||||||
|
|
Loading…
Add table
Reference in a new issue