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

regulator: core: forward undervoltage events downstream by default

Forward critical supply events downstream so consumers can react in
time. An under-voltage event on an upstream rail may otherwise never
reach end devices (e.g. eMMC).

Register a notifier on a regulator's supply when the supply is resolved,
and forward only REGULATOR_EVENT_UNDER_VOLTAGE to the consumer's notifier
chain. Event handling is deferred to process context via a workqueue; the
consumer rdev is lifetime-pinned and the rdev lock is held while calling
the notifier chain. The notifier is unregistered on regulator teardown.

No DT/UAPI changes. Behavior applies to all regulators with a supply.

Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
Link: https://patch.msgid.link/20251001105650.2391477-1-o.rempel@pengutronix.de
Signed-off-by: Mark Brown <broonie@kernel.org>

authored by

Oleksij Rempel and committed by
Mark Brown
433e294c 6277a486

+127
+124
drivers/regulator/core.c
··· 83 83 const char *alias_supply; 84 84 }; 85 85 86 + /* 87 + * Work item used to forward regulator events. 88 + * 89 + * @work: workqueue entry 90 + * @rdev: regulator device to notify (consumer receiving the forwarded event) 91 + * @event: event code to be forwarded 92 + */ 93 + struct regulator_event_work { 94 + struct work_struct work; 95 + struct regulator_dev *rdev; 96 + unsigned long event; 97 + }; 98 + 86 99 static int _regulator_is_enabled(struct regulator_dev *rdev); 87 100 static int _regulator_disable(struct regulator *regulator); 88 101 static int _regulator_get_error_flags(struct regulator_dev *rdev, unsigned int *flags); ··· 1672 1659 } 1673 1660 1674 1661 /** 1662 + * regulator_event_work_fn - process a deferred regulator event 1663 + * @work: work_struct queued by the notifier 1664 + * 1665 + * Calls the regulator's notifier chain in process context while holding 1666 + * the rdev lock, then releases the device reference. 1667 + */ 1668 + static void regulator_event_work_fn(struct work_struct *work) 1669 + { 1670 + struct regulator_event_work *rew = 1671 + container_of(work, struct regulator_event_work, work); 1672 + struct regulator_dev *rdev = rew->rdev; 1673 + int ret; 1674 + 1675 + regulator_lock(rdev); 1676 + ret = regulator_notifier_call_chain(rdev, rew->event, NULL); 1677 + regulator_unlock(rdev); 1678 + if (ret == NOTIFY_BAD) 1679 + dev_err(rdev_get_dev(rdev), "failed to forward regulator event\n"); 1680 + 1681 + put_device(rdev_get_dev(rdev)); 1682 + kfree(rew); 1683 + } 1684 + 1685 + /** 1686 + * regulator_event_forward_notifier - notifier callback for supply events 1687 + * @nb: notifier block embedded in the regulator 1688 + * @event: regulator event code 1689 + * @data: unused 1690 + * 1691 + * Packages the event into a work item and schedules it in process context. 1692 + * Takes a reference on @rdev->dev to pin the regulator until the work 1693 + * completes (see put_device() in the worker). 1694 + * 1695 + * Return: NOTIFY_OK on success, NOTIFY_DONE for events that are not forwarded. 1696 + */ 1697 + static int regulator_event_forward_notifier(struct notifier_block *nb, 1698 + unsigned long event, 1699 + void __always_unused *data) 1700 + { 1701 + struct regulator_dev *rdev = container_of(nb, struct regulator_dev, 1702 + supply_fwd_nb); 1703 + struct regulator_event_work *rew; 1704 + 1705 + switch (event) { 1706 + case REGULATOR_EVENT_UNDER_VOLTAGE: 1707 + break; 1708 + default: 1709 + /* Only forward allowed events downstream. */ 1710 + return NOTIFY_DONE; 1711 + } 1712 + 1713 + rew = kmalloc(sizeof(*rew), GFP_ATOMIC); 1714 + if (!rew) 1715 + return NOTIFY_DONE; 1716 + 1717 + get_device(rdev_get_dev(rdev)); 1718 + rew->rdev = rdev; 1719 + rew->event = event; 1720 + INIT_WORK(&rew->work, regulator_event_work_fn); 1721 + 1722 + queue_work(system_highpri_wq, &rew->work); 1723 + 1724 + return NOTIFY_OK; 1725 + } 1726 + 1727 + /** 1728 + * register_regulator_event_forwarding - enable supply event forwarding 1729 + * @rdev: regulator device 1730 + * 1731 + * Registers a notifier on the regulator's supply so that supply events 1732 + * are forwarded to the consumer regulator via the deferred work handler. 1733 + * 1734 + * Return: 0 on success, -EALREADY if already enabled, or a negative error code. 1735 + */ 1736 + static int register_regulator_event_forwarding(struct regulator_dev *rdev) 1737 + { 1738 + int ret; 1739 + 1740 + if (!rdev->supply) 1741 + return 0; /* top-level regulator: nothing to forward */ 1742 + 1743 + if (rdev->supply_fwd_nb.notifier_call) 1744 + return -EALREADY; 1745 + 1746 + rdev->supply_fwd_nb.notifier_call = regulator_event_forward_notifier; 1747 + 1748 + ret = regulator_register_notifier(rdev->supply, &rdev->supply_fwd_nb); 1749 + if (ret) { 1750 + dev_err(&rdev->dev, "failed to register supply notifier: %pe\n", 1751 + ERR_PTR(ret)); 1752 + rdev->supply_fwd_nb.notifier_call = NULL; 1753 + return ret; 1754 + } 1755 + 1756 + return 0; 1757 + } 1758 + 1759 + /** 1675 1760 * set_supply - set regulator supply regulator 1676 1761 * @rdev: regulator (locked) 1677 1762 * @supply_rdev: supply regulator (locked)) ··· 2254 2143 put_device(&r->dev); 2255 2144 goto out; 2256 2145 } 2146 + 2147 + /* 2148 + * Automatically register for event forwarding from the new supply. 2149 + * This creates the downstream propagation link for events like 2150 + * under-voltage. 2151 + */ 2152 + ret = register_regulator_event_forwarding(rdev); 2153 + if (ret < 0) 2154 + rdev_warn(rdev, "Failed to register event forwarding: %pe\n", 2155 + ERR_PTR(ret)); 2257 2156 2258 2157 regulator_unlock_two(rdev, r, &ww_ctx); 2259 2158 ··· 6152 6031 return; 6153 6032 6154 6033 if (rdev->supply) { 6034 + regulator_unregister_notifier(rdev->supply, 6035 + &rdev->supply_fwd_nb); 6036 + 6155 6037 while (rdev->use_count--) 6156 6038 regulator_disable(rdev->supply); 6157 6039 regulator_put(rdev->supply);
+3
include/linux/regulator/driver.h
··· 658 658 spinlock_t err_lock; 659 659 660 660 int pw_requested_mW; 661 + 662 + /* regulator notification forwarding */ 663 + struct notifier_block supply_fwd_nb; 661 664 }; 662 665 663 666 /*