unsigned long event;
};
+static int _regulator_enable(struct regulator *regulator);
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);
/**
* set_machine_constraints - sets regulator constraints
* @rdev: regulator source
+ * @is_locked: whether or not this is called with locks held already
*
* Allows platform initialisation code to define and constrain
* regulator circuits e.g. valid voltage/current ranges, etc. NOTE:
*
* Return: 0 on success or a negative error number on failure.
*/
-static int set_machine_constraints(struct regulator_dev *rdev)
+static int set_machine_constraints(struct regulator_dev *rdev,
+ bool is_locked)
{
int ret = 0;
const struct regulator_ops *ops = rdev->desc->ops;
if (rdev->supply &&
(rdev->constraints->always_on ||
!regulator_is_enabled(rdev->supply))) {
- ret = regulator_enable(rdev->supply);
+ ret = (is_locked
+ ? _regulator_enable(rdev->supply)
+ : regulator_enable(rdev->supply));
if (ret < 0) {
_regulator_put(rdev->supply);
rdev->supply = NULL;
return 0;
}
+static void unregister_regulator_event_forwarding(struct regulator_dev *rdev)
+{
+ if (!rdev->supply_fwd_nb.notifier_call)
+ return;
+
+ regulator_unregister_notifier(rdev->supply, &rdev->supply_fwd_nb);
+ rdev->supply_fwd_nb.notifier_call = NULL;
+}
+
/**
* set_supply - set regulator supply regulator
* @rdev: regulator (locked)
struct regulator_dev *r;
struct device *dev = rdev->dev.parent;
struct ww_acquire_ctx ww_ctx;
+ struct regulator *supply;
+ bool do_final_setup;
int ret = 0;
/* No supply to resolve? */
return 0;
/* Supply already resolved? (fast-path without locking contention) */
- if (rdev->supply)
+ if (rdev->supply && !rdev->constraints_pending)
return 0;
/* first do a dt based lookup on the node described in the virtual
/* Supply just resolved by a concurrent task? */
if (rdev->supply) {
+ /* Constraints might still be pending due to concurrency. */
+ bool done = !rdev->constraints_pending;
+
+ supply = rdev->supply;
+
regulator_unlock_two(rdev, r, &ww_ctx);
put_device(&r->dev);
- goto out;
- }
- ret = set_supply(rdev, r);
- if (ret < 0) {
+ /*
+ * Supply resolved by concurrent task, and constraints set as
+ * well (or not required): fast path.
+ */
+ if (done)
+ goto out;
+
+ do_final_setup = false;
+ } else {
+ ret = set_supply(rdev, r);
+ if (ret < 0) {
+ regulator_unlock_two(rdev, r, &ww_ctx);
+ put_device(&r->dev);
+ goto out;
+ }
+
+ supply = rdev->supply;
+
+ /*
+ * 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));
+
+ goto unset_supply;
+ }
+
regulator_unlock_two(rdev, r, &ww_ctx);
- put_device(&r->dev);
- goto out;
+
+ do_final_setup = true;
}
/*
- * Automatically register for event forwarding from the new supply.
- * This creates the downstream propagation link for events like
- * under-voltage.
+ * Now that we have the supply, we can retry setting the machine
+ * constraints, if necessary.
*/
- ret = register_regulator_event_forwarding(rdev);
- if (ret < 0) {
- struct regulator *supply;
-
- rdev_warn(rdev, "Failed to register event forwarding: %pe\n",
- ERR_PTR(ret));
-
- supply = rdev->supply;
- rdev->supply = NULL;
+ regulator_lock_dependent(rdev, &ww_ctx);
+ if (rdev->constraints_pending) {
+ if (!rdev->supply) {
+ /*
+ * Supply could have been released by another task that
+ * failed to set the constraints or event forwarding.
+ */
+ regulator_unlock_dependent(rdev, &ww_ctx);
+ ret = -EPROBE_DEFER;
+ goto out;
+ }
- regulator_unlock_two(rdev, supply->rdev, &ww_ctx);
+ ret = set_machine_constraints(rdev, true);
+ if (ret < 0) {
+ regulator_unlock_dependent(rdev, &ww_ctx);
+
+ rdev_warn(rdev,
+ "Failed to set machine constraints: %pe\n",
+ ERR_PTR(ret));
+
+ regulator_lock_two(rdev, r, &ww_ctx);
+
+ if (supply != rdev->supply) {
+ /*
+ * Supply could have been released by another
+ * task that got here before us. If it did, it
+ * will have released 'supply' (i.e. the
+ * previous rdev->supply) and we shouldn't do
+ * that again via unset_supply.
+ */
+ regulator_unlock_two(rdev, r, &ww_ctx);
+ goto out;
+ }
- regulator_put(supply);
- goto out;
+ unregister_regulator_event_forwarding(rdev);
+ rdev->constraints_pending = true;
+ goto unset_supply;
+ }
+ rdev->constraints_pending = false;
}
+ regulator_unlock_dependent(rdev, &ww_ctx);
- regulator_unlock_two(rdev, r, &ww_ctx);
+ if (!do_final_setup)
+ goto out;
/* rdev->supply was created in set_supply() */
- link_and_create_debugfs(rdev->supply, r, &rdev->dev);
+ link_and_create_debugfs(rdev->supply, rdev->supply->rdev, &rdev->dev);
out:
return ret;
+
+unset_supply:
+ lockdep_assert_held_once(&rdev->mutex.base);
+ lockdep_assert_held_once(&r->mutex.base);
+ rdev->supply = NULL;
+ regulator_unlock_two(rdev, supply->rdev, &ww_ctx);
+
+ regulator_put(supply);
+
+ return ret;
}
/* common pre-checks for regulator requests */
dangling_of_gpiod = false;
}
- ret = set_machine_constraints(rdev);
+ ret = set_machine_constraints(rdev, false);
if (ret == -EPROBE_DEFER) {
/* Regulator might be in bypass mode or an always-on or boot-on
* regulator and so needs its supply to set the constraints or
rdev->supply_name);
ret = regulator_resolve_supply(rdev);
if (!ret)
- ret = set_machine_constraints(rdev);
+ ret = set_machine_constraints(rdev, false);
else
rdev_dbg(rdev, "unable to resolve supply early: %pe\n",
ERR_PTR(ret));
tried_supply_resolve = true;
}
- if (ret < 0)
- goto wash;
+ if (ret < 0) {
+ if (ret != -EPROBE_DEFER)
+ goto wash;
+ rdev->constraints_pending = true;
+ }
ret = regulator_init_coupling(rdev);
if (ret < 0)