]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
regulator: core: forward undervoltage events downstream by default
authorOleksij Rempel <o.rempel@pengutronix.de>
Wed, 1 Oct 2025 10:56:49 +0000 (12:56 +0200)
committerMark Brown <broonie@kernel.org>
Wed, 15 Oct 2025 09:48:58 +0000 (10:48 +0100)
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>
drivers/regulator/core.c
include/linux/regulator/driver.h

index dd7b10e768c06c83d2a4fb5dfd0fce8d796c9185..84bc38911dba308539a25dd3af30a5c24802b890 100644 (file)
@@ -83,6 +83,19 @@ struct regulator_supply_alias {
        const char *alias_supply;
 };
 
+/*
+ * Work item used to forward regulator events.
+ *
+ * @work: workqueue entry
+ * @rdev: regulator device to notify (consumer receiving the forwarded event)
+ * @event: event code to be forwarded
+ */
+struct regulator_event_work {
+       struct work_struct work;
+       struct regulator_dev *rdev;
+       unsigned long event;
+};
+
 static int _regulator_is_enabled(struct regulator_dev *rdev);
 static int _regulator_disable(struct regulator *regulator);
 static int _regulator_get_error_flags(struct regulator_dev *rdev, unsigned int *flags);
@@ -1658,6 +1671,104 @@ static int set_machine_constraints(struct regulator_dev *rdev)
        return 0;
 }
 
+/**
+ * regulator_event_work_fn - process a deferred regulator event
+ * @work: work_struct queued by the notifier
+ *
+ * Calls the regulator's notifier chain in process context while holding
+ * the rdev lock, then releases the device reference.
+ */
+static void regulator_event_work_fn(struct work_struct *work)
+{
+       struct regulator_event_work *rew =
+               container_of(work, struct regulator_event_work, work);
+       struct regulator_dev *rdev = rew->rdev;
+       int ret;
+
+       regulator_lock(rdev);
+       ret = regulator_notifier_call_chain(rdev, rew->event, NULL);
+       regulator_unlock(rdev);
+       if (ret == NOTIFY_BAD)
+               dev_err(rdev_get_dev(rdev), "failed to forward regulator event\n");
+
+       put_device(rdev_get_dev(rdev));
+       kfree(rew);
+}
+
+/**
+ * regulator_event_forward_notifier - notifier callback for supply events
+ * @nb:    notifier block embedded in the regulator
+ * @event: regulator event code
+ * @data:  unused
+ *
+ * Packages the event into a work item and schedules it in process context.
+ * Takes a reference on @rdev->dev to pin the regulator until the work
+ * completes (see put_device() in the worker).
+ *
+ * Return: NOTIFY_OK on success, NOTIFY_DONE for events that are not forwarded.
+ */
+static int regulator_event_forward_notifier(struct notifier_block *nb,
+                                           unsigned long event,
+                                           void __always_unused *data)
+{
+       struct regulator_dev *rdev = container_of(nb, struct regulator_dev,
+                                                 supply_fwd_nb);
+       struct regulator_event_work *rew;
+
+       switch (event) {
+       case REGULATOR_EVENT_UNDER_VOLTAGE:
+               break;
+       default:
+               /* Only forward allowed events downstream. */
+               return NOTIFY_DONE;
+       }
+
+       rew = kmalloc(sizeof(*rew), GFP_ATOMIC);
+       if (!rew)
+               return NOTIFY_DONE;
+
+       get_device(rdev_get_dev(rdev));
+       rew->rdev = rdev;
+       rew->event = event;
+       INIT_WORK(&rew->work, regulator_event_work_fn);
+
+       queue_work(system_highpri_wq, &rew->work);
+
+       return NOTIFY_OK;
+}
+
+/**
+ * register_regulator_event_forwarding - enable supply event forwarding
+ * @rdev: regulator device
+ *
+ * Registers a notifier on the regulator's supply so that supply events
+ * are forwarded to the consumer regulator via the deferred work handler.
+ *
+ * Return: 0 on success, -EALREADY if already enabled, or a negative error code.
+ */
+static int register_regulator_event_forwarding(struct regulator_dev *rdev)
+{
+       int ret;
+
+       if (!rdev->supply)
+               return 0; /* top-level regulator: nothing to forward */
+
+       if (rdev->supply_fwd_nb.notifier_call)
+               return -EALREADY;
+
+       rdev->supply_fwd_nb.notifier_call = regulator_event_forward_notifier;
+
+       ret = regulator_register_notifier(rdev->supply, &rdev->supply_fwd_nb);
+       if (ret) {
+               dev_err(&rdev->dev, "failed to register supply notifier: %pe\n",
+                       ERR_PTR(ret));
+               rdev->supply_fwd_nb.notifier_call = NULL;
+               return ret;
+       }
+
+       return 0;
+}
+
 /**
  * set_supply - set regulator supply regulator
  * @rdev: regulator (locked)
@@ -2144,6 +2255,16 @@ static int regulator_resolve_supply(struct regulator_dev *rdev)
                goto out;
        }
 
+       /*
+        * Automatically register for event forwarding from the new supply.
+        * This creates the downstream propagation link for events like
+        * under-voltage.
+        */
+       ret = register_regulator_event_forwarding(rdev);
+       if (ret < 0)
+               rdev_warn(rdev, "Failed to register event forwarding: %pe\n",
+                         ERR_PTR(ret));
+
        regulator_unlock_two(rdev, r, &ww_ctx);
 
        /* rdev->supply was created in set_supply() */
@@ -6031,6 +6152,9 @@ void regulator_unregister(struct regulator_dev *rdev)
                return;
 
        if (rdev->supply) {
+               regulator_unregister_notifier(rdev->supply,
+                                             &rdev->supply_fwd_nb);
+
                while (rdev->use_count--)
                        regulator_disable(rdev->supply);
                regulator_put(rdev->supply);
index 4a216fdba354f88e702c4f07c2ad72be2eea1953..978cf593b6624228fe1fd9c2a3e186b53ef172f8 100644 (file)
@@ -658,6 +658,9 @@ struct regulator_dev {
        spinlock_t err_lock;
 
        int pw_requested_mW;
+
+       /* regulator notification forwarding */
+       struct notifier_block supply_fwd_nb;
 };
 
 /*