summaryrefslogtreecommitdiff
path: root/drivers/regulator/core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/regulator/core.c')
-rw-r--r--drivers/regulator/core.c169
1 files changed, 157 insertions, 12 deletions
diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c
index dd7b10e768c0..f4987f54e01b 100644
--- a/drivers/regulator/core.c
+++ b/drivers/regulator/core.c
@@ -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);
@@ -1618,6 +1631,8 @@ static int set_machine_constraints(struct regulator_dev *rdev)
* and we have control then make sure it is enabled.
*/
if (rdev->constraints->always_on || rdev->constraints->boot_on) {
+ bool supply_enabled = false;
+
/* If we want to enable this regulator, make sure that we know
* the supplying regulator.
*/
@@ -1637,11 +1652,14 @@ static int set_machine_constraints(struct regulator_dev *rdev)
rdev->supply = NULL;
return ret;
}
+ supply_enabled = true;
}
ret = _regulator_do_enable(rdev);
if (ret < 0 && ret != -EINVAL) {
rdev_err(rdev, "failed to enable: %pe\n", ERR_PTR(ret));
+ if (supply_enabled)
+ regulator_disable(rdev->supply);
return ret;
}
@@ -1659,6 +1677,104 @@ static int set_machine_constraints(struct regulator_dev *rdev)
}
/**
+ * 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)
* @supply_rdev: supply regulator (locked))
@@ -1942,6 +2058,7 @@ static void regulator_supply_alias(struct device **dev, const char **supply)
{
struct regulator_supply_alias *map;
+ mutex_lock(&regulator_list_mutex);
map = regulator_find_supply_alias(*dev, *supply);
if (map) {
dev_dbg(*dev, "Mapping supply %s to %s,%s\n",
@@ -1950,6 +2067,7 @@ static void regulator_supply_alias(struct device **dev, const char **supply)
*dev = map->alias_dev;
*supply = map->alias_supply;
}
+ mutex_unlock(&regulator_list_mutex);
}
static int regulator_match(struct device *dev, const void *data)
@@ -2144,6 +2262,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() */
@@ -2492,22 +2620,26 @@ int regulator_register_supply_alias(struct device *dev, const char *id,
const char *alias_id)
{
struct regulator_supply_alias *map;
+ struct regulator_supply_alias *new_map;
- map = regulator_find_supply_alias(dev, id);
- if (map)
- return -EEXIST;
-
- map = kzalloc(sizeof(struct regulator_supply_alias), GFP_KERNEL);
- if (!map)
+ new_map = kzalloc(sizeof(struct regulator_supply_alias), GFP_KERNEL);
+ if (!new_map)
return -ENOMEM;
- map->src_dev = dev;
- map->src_supply = id;
- map->alias_dev = alias_dev;
- map->alias_supply = alias_id;
-
- list_add(&map->list, &regulator_supply_alias_list);
+ mutex_lock(&regulator_list_mutex);
+ map = regulator_find_supply_alias(dev, id);
+ if (map) {
+ mutex_unlock(&regulator_list_mutex);
+ kfree(new_map);
+ return -EEXIST;
+ }
+ new_map->src_dev = dev;
+ new_map->src_supply = id;
+ new_map->alias_dev = alias_dev;
+ new_map->alias_supply = alias_id;
+ list_add(&new_map->list, &regulator_supply_alias_list);
+ mutex_unlock(&regulator_list_mutex);
pr_info("Adding alias for supply %s,%s -> %s,%s\n",
id, dev_name(dev), alias_id, dev_name(alias_dev));
@@ -2527,11 +2659,13 @@ void regulator_unregister_supply_alias(struct device *dev, const char *id)
{
struct regulator_supply_alias *map;
+ mutex_lock(&regulator_list_mutex);
map = regulator_find_supply_alias(dev, id);
if (map) {
list_del(&map->list);
kfree(map);
}
+ mutex_unlock(&regulator_list_mutex);
}
EXPORT_SYMBOL_GPL(regulator_unregister_supply_alias);
@@ -2616,6 +2750,13 @@ static int regulator_ena_gpio_request(struct regulator_dev *rdev,
mutex_lock(&regulator_list_mutex);
+ if (gpiod_is_shared(gpiod))
+ /*
+ * The sharing of this GPIO pin is managed internally by
+ * GPIOLIB. We don't need to keep track of its enable count.
+ */
+ goto skip_compare;
+
list_for_each_entry(pin, &regulator_ena_gpio_list, list) {
if (gpiod_is_equal(pin->gpiod, gpiod)) {
rdev_dbg(rdev, "GPIO is already used\n");
@@ -2628,6 +2769,7 @@ static int regulator_ena_gpio_request(struct regulator_dev *rdev,
return -ENOMEM;
}
+skip_compare:
pin = new_pin;
new_pin = NULL;
@@ -6031,6 +6173,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);