Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

power: supply: ucs1002: fix some health status issues

Some fault events like the over-current condition will get resolved
by the hardware, by e.g. disabling the port. As the status in the
interrupt status register is cleared on read when the fault is resolved,
the sysfs health property will only contain the correct health status
for the first time it is read after such an event, even if the actual
fault condition (like a VBUS short) still persists. To reflect this
properly in the property we cache the last health status and only update
the cache when a actual change happens, i.e. the ERR bit in the status
register flips, as this one properly reflects a continued fault condition.

The ALERT pin however, is not driven by the ERR status, but by the actual
fault status, so the pin will change back to it's default state when the
hardware has automatically resolved the fault by cutting the power. Thus
we never get an IRQ when the actual fault condition has been resolved and
the ERR status bit has been cleared in auto-recovery mode. To get this
information we need to poll the interrupt status register after some time
to see if the fault is gone and update our cache in that case.

To avoid any additional locking, we handle both paths (IRQ firing and
delayed polling) through the same single-threaded delayed work.

Signed-off-by: Lucas Stach <l.stach@pengutronix.de>
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>

authored by

Lucas Stach and committed by
Sebastian Reichel
81196e2e 3c8b5fb9

+43 -32
+43 -32
drivers/power/supply/ucs1002_power.c
··· 38 38 39 39 /* Interrupt Status */ 40 40 #define UCS1002_REG_INTERRUPT_STATUS 0x10 41 + # define F_ERR BIT(7) 41 42 # define F_DISCHARGE_ERR BIT(6) 42 43 # define F_RESET BIT(5) 43 44 # define F_MIN_KEEP_OUT BIT(4) ··· 104 103 struct regulator_dev *rdev; 105 104 bool present; 106 105 bool output_disable; 106 + struct delayed_work health_poll; 107 + int health; 108 + 107 109 }; 108 110 109 111 static enum power_supply_property ucs1002_props[] = { ··· 366 362 return 0; 367 363 } 368 364 369 - static int ucs1002_get_health(struct ucs1002_info *info, 370 - union power_supply_propval *val) 371 - { 372 - unsigned int reg; 373 - int ret, health; 374 - 375 - ret = regmap_read(info->regmap, UCS1002_REG_INTERRUPT_STATUS, &reg); 376 - if (ret) 377 - return ret; 378 - 379 - if (reg & F_TSD) 380 - health = POWER_SUPPLY_HEALTH_OVERHEAT; 381 - else if (reg & (F_OVER_VOLT | F_BACK_VOLT)) 382 - health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; 383 - else if (reg & F_OVER_ILIM) 384 - health = POWER_SUPPLY_HEALTH_OVERCURRENT; 385 - else if (reg & (F_DISCHARGE_ERR | F_MIN_KEEP_OUT)) 386 - health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; 387 - else 388 - health = POWER_SUPPLY_HEALTH_GOOD; 389 - 390 - val->intval = health; 391 - 392 - return 0; 393 - } 394 - 395 365 static int ucs1002_get_property(struct power_supply *psy, 396 366 enum power_supply_property psp, 397 367 union power_supply_propval *val) ··· 384 406 case POWER_SUPPLY_PROP_USB_TYPE: 385 407 return ucs1002_get_usb_type(info, val); 386 408 case POWER_SUPPLY_PROP_HEALTH: 387 - return ucs1002_get_health(info, val); 409 + return val->intval = info->health; 388 410 case POWER_SUPPLY_PROP_PRESENT: 389 411 val->intval = info->present; 390 412 return 0; ··· 436 458 .num_properties = ARRAY_SIZE(ucs1002_props), 437 459 }; 438 460 461 + static void ucs1002_health_poll(struct work_struct *work) 462 + { 463 + struct ucs1002_info *info = container_of(work, struct ucs1002_info, 464 + health_poll.work); 465 + int ret; 466 + u32 reg; 467 + 468 + ret = regmap_read(info->regmap, UCS1002_REG_INTERRUPT_STATUS, &reg); 469 + if (ret) 470 + return; 471 + 472 + /* bad health and no status change, just schedule us again in a while */ 473 + if ((reg & F_ERR) && info->health != POWER_SUPPLY_HEALTH_GOOD) { 474 + schedule_delayed_work(&info->health_poll, 475 + msecs_to_jiffies(2000)); 476 + return; 477 + } 478 + 479 + if (reg & F_TSD) 480 + info->health = POWER_SUPPLY_HEALTH_OVERHEAT; 481 + else if (reg & (F_OVER_VOLT | F_BACK_VOLT)) 482 + info->health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; 483 + else if (reg & F_OVER_ILIM) 484 + info->health = POWER_SUPPLY_HEALTH_OVERCURRENT; 485 + else if (reg & (F_DISCHARGE_ERR | F_MIN_KEEP_OUT)) 486 + info->health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; 487 + else 488 + info->health = POWER_SUPPLY_HEALTH_GOOD; 489 + 490 + sysfs_notify(&info->charger->dev.kobj, NULL, "health"); 491 + } 492 + 439 493 static irqreturn_t ucs1002_charger_irq(int irq, void *data) 440 494 { 441 495 int ret, regval; ··· 494 484 { 495 485 struct ucs1002_info *info = data; 496 486 497 - power_supply_changed(info->charger); 487 + mod_delayed_work(system_wq, &info->health_poll, 0); 498 488 499 489 return IRQ_HANDLED; 500 490 } ··· 642 632 return ret; 643 633 } 644 634 635 + info->health = POWER_SUPPLY_HEALTH_GOOD; 636 + INIT_DELAYED_WORK(&info->health_poll, ucs1002_health_poll); 637 + 645 638 if (irq_a_det > 0) { 646 639 ret = devm_request_threaded_irq(dev, irq_a_det, NULL, 647 640 ucs1002_charger_irq, ··· 658 645 } 659 646 660 647 if (irq_alert > 0) { 661 - ret = devm_request_threaded_irq(dev, irq_alert, NULL, 662 - ucs1002_alert_irq, 663 - IRQF_ONESHOT, 664 - "ucs1002-alert", info); 648 + ret = devm_request_irq(dev, irq_alert, ucs1002_alert_irq, 649 + 0,"ucs1002-alert", info); 665 650 if (ret) { 666 651 dev_err(dev, "Failed to request ALERT threaded irq: %d\n", 667 652 ret);