Commit 57ab80e6 authored by Liam Breck's avatar Liam Breck Committed by Ben Hutchings

power: supply: bq24190_charger: Don't read fault register outside irq_handle_thread()

commit 68abfb80 upstream.

Caching the fault register after a single I2C read may not keep an accurate
value.

Fix by doing two reads in irq_handle_thread() and using the cached value
elsewhere. If a safety timer fault later clears itself, we apparently don't get
an interrupt (INT), however other interrupts would refresh the register cache.

From the data sheet: "When a fault occurs, the charger device sends out INT
 and keeps the fault state in REG09 until the host reads the fault register.
 Before the host reads REG09 and all the faults are cleared, the charger
 device would not send any INT upon new faults. In order to read the
 current fault status, the host has to read REG09 two times consecutively.
 The 1st reads fault register status from the last read [1] and the 2nd reads
 the current fault register status."

[1] presumably a typo; should be "last fault"

Fixes: d7bf353f ("bq24190_charger: Add support for TI BQ24190 Battery Charger")
Signed-off-by: default avatarLiam Breck <kernel@networkimprov.net>
Acked-by: default avatarMark Greer <mgreer@animalcreek.com>
Acked-by: default avatarTony Lindgren <tony@atomide.com>
Signed-off-by: default avatarSebastian Reichel <sre@kernel.org>
[bwh: Backported to 3.16: adjust filename]
Signed-off-by: default avatarBen Hutchings <ben@decadent.org.uk>
parent 167dc75c
...@@ -144,10 +144,7 @@ ...@@ -144,10 +144,7 @@
* so the first read after a fault returns the latched value and subsequent * so the first read after a fault returns the latched value and subsequent
* reads return the current value. In order to return the fault status * reads return the current value. In order to return the fault status
* to the user, have the interrupt handler save the reg's value and retrieve * to the user, have the interrupt handler save the reg's value and retrieve
* it in the appropriate health/status routine. Each routine has its own * it in the appropriate health/status routine.
* flag indicating whether it should use the value stored by the last run
* of the interrupt handler or do an actual reg read. That way each routine
* can report back whatever fault may have occured.
*/ */
struct bq24190_dev_info { struct bq24190_dev_info {
struct i2c_client *client; struct i2c_client *client;
...@@ -159,9 +156,6 @@ struct bq24190_dev_info { ...@@ -159,9 +156,6 @@ struct bq24190_dev_info {
unsigned int gpio_int; unsigned int gpio_int;
unsigned int irq; unsigned int irq;
struct mutex f_reg_lock; struct mutex f_reg_lock;
bool charger_health_valid;
bool battery_health_valid;
bool battery_status_valid;
u8 f_reg; u8 f_reg;
u8 ss_reg; u8 ss_reg;
u8 watchdog; u8 watchdog;
...@@ -637,22 +631,12 @@ static int bq24190_charger_get_health(struct bq24190_dev_info *bdi, ...@@ -637,22 +631,12 @@ static int bq24190_charger_get_health(struct bq24190_dev_info *bdi,
union power_supply_propval *val) union power_supply_propval *val)
{ {
u8 v; u8 v;
int health, ret; int health;
mutex_lock(&bdi->f_reg_lock); mutex_lock(&bdi->f_reg_lock);
if (bdi->charger_health_valid) {
v = bdi->f_reg; v = bdi->f_reg;
bdi->charger_health_valid = false;
mutex_unlock(&bdi->f_reg_lock);
} else {
mutex_unlock(&bdi->f_reg_lock); mutex_unlock(&bdi->f_reg_lock);
ret = bq24190_read(bdi, BQ24190_REG_F, &v);
if (ret < 0)
return ret;
}
if (v & BQ24190_REG_F_BOOST_FAULT_MASK) { if (v & BQ24190_REG_F_BOOST_FAULT_MASK) {
/* /*
* This could be over-current or over-voltage but there's * This could be over-current or over-voltage but there's
...@@ -943,18 +927,8 @@ static int bq24190_battery_get_status(struct bq24190_dev_info *bdi, ...@@ -943,18 +927,8 @@ static int bq24190_battery_get_status(struct bq24190_dev_info *bdi,
int status, ret; int status, ret;
mutex_lock(&bdi->f_reg_lock); mutex_lock(&bdi->f_reg_lock);
if (bdi->battery_status_valid) {
chrg_fault = bdi->f_reg; chrg_fault = bdi->f_reg;
bdi->battery_status_valid = false;
mutex_unlock(&bdi->f_reg_lock); mutex_unlock(&bdi->f_reg_lock);
} else {
mutex_unlock(&bdi->f_reg_lock);
ret = bq24190_read(bdi, BQ24190_REG_F, &chrg_fault);
if (ret < 0)
return ret;
}
chrg_fault &= BQ24190_REG_F_CHRG_FAULT_MASK; chrg_fault &= BQ24190_REG_F_CHRG_FAULT_MASK;
chrg_fault >>= BQ24190_REG_F_CHRG_FAULT_SHIFT; chrg_fault >>= BQ24190_REG_F_CHRG_FAULT_SHIFT;
...@@ -1002,22 +976,12 @@ static int bq24190_battery_get_health(struct bq24190_dev_info *bdi, ...@@ -1002,22 +976,12 @@ static int bq24190_battery_get_health(struct bq24190_dev_info *bdi,
union power_supply_propval *val) union power_supply_propval *val)
{ {
u8 v; u8 v;
int health, ret; int health;
mutex_lock(&bdi->f_reg_lock); mutex_lock(&bdi->f_reg_lock);
if (bdi->battery_health_valid) {
v = bdi->f_reg; v = bdi->f_reg;
bdi->battery_health_valid = false;
mutex_unlock(&bdi->f_reg_lock);
} else {
mutex_unlock(&bdi->f_reg_lock); mutex_unlock(&bdi->f_reg_lock);
ret = bq24190_read(bdi, BQ24190_REG_F, &v);
if (ret < 0)
return ret;
}
if (v & BQ24190_REG_F_BAT_FAULT_MASK) { if (v & BQ24190_REG_F_BAT_FAULT_MASK) {
health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
} else { } else {
...@@ -1211,7 +1175,7 @@ static irqreturn_t bq24190_irq_handler_thread(int irq, void *data) ...@@ -1211,7 +1175,7 @@ static irqreturn_t bq24190_irq_handler_thread(int irq, void *data)
| BQ24190_REG_F_NTC_FAULT_MASK; | BQ24190_REG_F_NTC_FAULT_MASK;
bool alert_charger = false, alert_battery = false; bool alert_charger = false, alert_battery = false;
u8 ss_reg = 0, f_reg = 0; u8 ss_reg = 0, f_reg = 0;
int ret; int i, ret;
pm_runtime_get_sync(bdi->dev); pm_runtime_get_sync(bdi->dev);
...@@ -1241,32 +1205,34 @@ static irqreturn_t bq24190_irq_handler_thread(int irq, void *data) ...@@ -1241,32 +1205,34 @@ static irqreturn_t bq24190_irq_handler_thread(int irq, void *data)
alert_battery = true; alert_battery = true;
if ((bdi->ss_reg & ~battery_mask_ss) != (ss_reg & ~battery_mask_ss)) if ((bdi->ss_reg & ~battery_mask_ss) != (ss_reg & ~battery_mask_ss))
alert_charger = true; alert_charger = true;
bdi->ss_reg = ss_reg; bdi->ss_reg = ss_reg;
} }
mutex_lock(&bdi->f_reg_lock); i = 0;
do {
ret = bq24190_read(bdi, BQ24190_REG_F, &f_reg); ret = bq24190_read(bdi, BQ24190_REG_F, &f_reg);
if (ret < 0) { if (ret < 0) {
mutex_unlock(&bdi->f_reg_lock);
dev_err(bdi->dev, "Can't read F reg: %d\n", ret); dev_err(bdi->dev, "Can't read F reg: %d\n", ret);
goto out; goto out;
} }
} while (f_reg && ++i < 2);
if (f_reg != bdi->f_reg) { if (f_reg != bdi->f_reg) {
dev_info(bdi->dev,
"Fault: boost %d, charge %d, battery %d, ntc %d\n",
!!(f_reg & BQ24190_REG_F_BOOST_FAULT_MASK),
!!(f_reg & BQ24190_REG_F_CHRG_FAULT_MASK),
!!(f_reg & BQ24190_REG_F_BAT_FAULT_MASK),
!!(f_reg & BQ24190_REG_F_NTC_FAULT_MASK));
mutex_lock(&bdi->f_reg_lock);
if ((bdi->f_reg & battery_mask_f) != (f_reg & battery_mask_f)) if ((bdi->f_reg & battery_mask_f) != (f_reg & battery_mask_f))
alert_battery = true; alert_battery = true;
if ((bdi->f_reg & ~battery_mask_f) != (f_reg & ~battery_mask_f)) if ((bdi->f_reg & ~battery_mask_f) != (f_reg & ~battery_mask_f))
alert_charger = true; alert_charger = true;
bdi->f_reg = f_reg; bdi->f_reg = f_reg;
bdi->charger_health_valid = true;
bdi->battery_health_valid = true;
bdi->battery_status_valid = true;
}
mutex_unlock(&bdi->f_reg_lock); mutex_unlock(&bdi->f_reg_lock);
}
if (alert_charger) if (alert_charger)
power_supply_changed(&bdi->charger); power_supply_changed(&bdi->charger);
...@@ -1386,9 +1352,6 @@ static int bq24190_probe(struct i2c_client *client, ...@@ -1386,9 +1352,6 @@ static int bq24190_probe(struct i2c_client *client,
mutex_init(&bdi->f_reg_lock); mutex_init(&bdi->f_reg_lock);
bdi->f_reg = 0; bdi->f_reg = 0;
bdi->ss_reg = BQ24190_REG_SS_VBUS_STAT_MASK; /* impossible state */ bdi->ss_reg = BQ24190_REG_SS_VBUS_STAT_MASK; /* impossible state */
bdi->charger_health_valid = false;
bdi->battery_health_valid = false;
bdi->battery_status_valid = false;
i2c_set_clientdata(client, bdi); i2c_set_clientdata(client, bdi);
...@@ -1497,9 +1460,6 @@ static int bq24190_pm_resume(struct device *dev) ...@@ -1497,9 +1460,6 @@ static int bq24190_pm_resume(struct device *dev)
bdi->f_reg = 0; bdi->f_reg = 0;
bdi->ss_reg = BQ24190_REG_SS_VBUS_STAT_MASK; /* impossible state */ bdi->ss_reg = BQ24190_REG_SS_VBUS_STAT_MASK; /* impossible state */
bdi->charger_health_valid = false;
bdi->battery_health_valid = false;
bdi->battery_status_valid = false;
pm_runtime_get_sync(bdi->dev); pm_runtime_get_sync(bdi->dev);
bq24190_register_reset(bdi); bq24190_register_reset(bdi);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment